Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Refactor Android implementation to use ContentObserver #43

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ rootProject.allprojects {
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 30
Expand All @@ -32,3 +33,10 @@ android {
disable 'InvalidPackage'
}
}
dependencies {
implementation "androidx.core:core-ktx:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
mavenCentral()
}
2 changes: 1 addition & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
package="com.flutter.moum.screenshot_callback">

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

</manifest>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,87 +1,70 @@
package com.flutter.moum.screenshot_callback;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

import android.os.Build;
import android.os.FileObserver;

import android.os.Handler;
import android.os.Looper;
//import android.util.Log;

import java.io.File;
import java.util.List;
import java.util.ArrayList;

public class ScreenshotCallbackPlugin implements MethodCallHandler {
private static MethodChannel channel;
ScreenshotCallbackPlugin(Context context) {
_context = context;
}

private static MethodChannel _channel;
private static final String _tag = "screenshot_callback";

private Handler handler;
private FileObserver fileObserver;
private String TAG = "tag";
private final Context _context;

private Handler _handler;
private ScreenshotDetector _detector;
private String _lastScreenshotName;

public static void registerWith(Registrar registrar) {
channel = new MethodChannel(registrar.messenger(), "flutter.moum/screenshot_callback");
channel.setMethodCallHandler(new ScreenshotCallbackPlugin());
_channel = new MethodChannel(registrar.messenger(), "flutter.moum/screenshot_callback");
_channel.setMethodCallHandler(new ScreenshotCallbackPlugin(registrar.context()));
}

@Override
public void onMethodCall(MethodCall call, Result result) {
//Log.d(TAG, "onMethodCall: ");

if (call.method.equals("initialize")) {
handler = new Handler(Looper.getMainLooper());
if (Build.VERSION.SDK_INT >= 29) {
//Log.d(TAG, "android x");
List<File> files = new ArrayList<File>();
for (Path path : Path.values()) {
files.add(new File(path.getPath()));
}
_handler = new Handler(Looper.getMainLooper());

fileObserver = new FileObserver(files, FileObserver.CREATE) {
@Override
public void onEvent(int event, String path) {
//Log.d(TAG, "androidX onEvent");
if (event == FileObserver.CREATE) {
handler.post(new Runnable() {
@Override
public void run() {
channel.invokeMethod("onCallback", null);
}
});
}
}
};
fileObserver.startWatching();
} else {
//Log.d(TAG, "android others");
for (Path path : Path.values()) {
//Log.d(TAG, "onMethodCall: "+path.getPath());
fileObserver = new FileObserver(path.getPath(), FileObserver.CREATE) {
@Override
public void onEvent(int event, String path) {
//Log.d(TAG, "android others onEvent");
if (event == FileObserver.CREATE) {
handler.post(new Runnable() {
@Override
public void run() {
channel.invokeMethod("onCallback", null);
}
});
_detector = new ScreenshotDetector(_context, new Function1<String, Unit>() {
@Override
public Unit invoke(String screenshotName) {
// Log.d(_tag, "onScreenshotDetected: " + screenshotName);

if (!screenshotName.equals(_lastScreenshotName)) {
_lastScreenshotName = screenshotName;
_handler.post(new Runnable() {
@Override
public void run() {
// Log.d(_tag, "onCallback: ");
_channel.invokeMethod("onCallback", null);
}
}
};
fileObserver.startWatching();
});
}

return null;
}
}
});
_detector.start();

result.success("initialize");
} else if (call.method.equals("dispose")) {
fileObserver.stopWatching();
_detector.stop();
_detector = null;
_lastScreenshotName = null;

result.success("dispose");
} else {
result.notImplemented();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.flutter.moum.screenshot_callback

import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore

class ScreenshotDetector(private val context: Context,
private val callback: (name: String) -> Unit) {

private var contentObserver: ContentObserver? = null

fun start() {
if (contentObserver == null) {
contentObserver = context.contentResolver.registerObserver()
}
}

fun stop() {
contentObserver?.let { context.contentResolver.unregisterContentObserver(it) }
contentObserver = null
}

private fun reportScreenshotsUpdate(uri: Uri) {
val screenshots: List<String> = queryScreenshots(uri)
if (screenshots.isNotEmpty()) {
callback.invoke(screenshots.last());
}
}

private fun queryScreenshots(uri: Uri): List<String> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
queryRelativeDataColumn(uri)
} else {
queryDataColumn(uri)
}
}

private fun queryDataColumn(uri: Uri): List<String> {
val screenshots = mutableListOf<String>()

val projection = arrayOf(
MediaStore.Images.Media.DATA
)
context.contentResolver.query(
uri,
projection,
null,
null,
null
)?.use { cursor ->
val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)

while (cursor.moveToNext()) {
val path = cursor.getString(dataColumn)
if (path.contains("screenshot", true)) {
screenshots.add(path)
}
}
}

return screenshots
}

private fun queryRelativeDataColumn(uri: Uri): List<String> {
val screenshots = mutableListOf<String>()

val projection = arrayOf(
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.RELATIVE_PATH
)
context.contentResolver.query(
uri,
projection,
null,
null,
null
)?.use { cursor ->
val relativePathColumn =
cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
val displayNameColumn =
cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
while (cursor.moveToNext()) {
val name = cursor.getString(displayNameColumn)
val relativePath = cursor.getString(relativePathColumn)
if (name.contains("screenshot", true) or
relativePath.contains("screenshot", true)
) {
screenshots.add(name)
}
}
}

return screenshots
}

private fun ContentResolver.registerObserver(): ContentObserver {
val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
uri?.let { reportScreenshotsUpdate(it) }
}
}
registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
return contentObserver
}
}
2 changes: 2 additions & 0 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
buildscript {
ext.kotlin_version = '1.5.0'
repositories {
google()
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ packages:
name: permission_handler
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.1+1"
version: "5.1.0+2"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "2.0.2"
plugin_platform_interface:
dependency: transitive
description:
Expand Down
4 changes: 2 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ packages:
name: permission_handler
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.1+1"
version: "5.1.0+2"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "2.0.2"
plugin_platform_interface:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ environment:
dependencies:
flutter:
sdk: flutter
permission_handler: ^5.0.1+1
permission_handler: ^5.1.0+2

dev_dependencies:
flutter_test:
Expand Down