From 444e6f7ec2001df44d49ebeb25a81bf5640b8284 Mon Sep 17 00:00:00 2001 From: Advait Mahashabde Date: Sun, 3 Aug 2025 20:24:16 -0700 Subject: [PATCH 1/2] Few more fixes --- gradle/publishing.gradle | 4 ++ .../linkedin/android/shaky/app/ShakyDemo.java | 4 ++ .../android/shaky/LifecycleCallbacks.java | 1 + .../android/shaky/ScreenCaptureManager.java | 65 ++++++++++++------- .../com/linkedin/android/shaky/Shaky.java | 13 ++++ 5 files changed, 64 insertions(+), 23 deletions(-) diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index 1c01285..feb4e7f 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -120,6 +120,10 @@ afterEvaluate { name = "local" url = uri("${project.buildDir}/repo") } + maven { + name = "mavenLocal" + url = uri("${System.getProperty('user.home')}/.m2/repository") + } } } diff --git a/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyDemo.java b/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyDemo.java index 320ce08..69af318 100644 --- a/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyDemo.java +++ b/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyDemo.java @@ -41,6 +41,10 @@ public class ShakyDemo extends FragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); + + // Initialize ScreenCaptureManager for the Shaky library + ((ShakyApplication) getApplication()).getShaky().initializeScreenCapture(this); + ViewCompat.setOnApplyWindowInsetsListener( getWindow().findViewById(R.id.demo_background), (v, insets) -> { diff --git a/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java b/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java index 52d6d13..b73d2a4 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java @@ -19,6 +19,7 @@ import android.app.Application; import android.os.Bundle; import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks { diff --git a/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java b/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java index 99a6405..de08379 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java @@ -11,13 +11,15 @@ import android.media.ImageReader; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; -import android.os.Build; import android.os.Handler; import android.view.Display; import android.view.WindowManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.fragment.app.FragmentActivity; import java.nio.ByteBuffer; @@ -27,8 +29,6 @@ */ public class ScreenCaptureManager { - private static final int REQUEST_CODE_MEDIA_PROJECTION = 1000; - private Context appContext; private MediaProjectionManager projectionManager; private MediaProjection mediaProjection; @@ -39,6 +39,7 @@ public class ScreenCaptureManager { private int screenDensity; private CaptureCallback captureCallback; + private ActivityResultLauncher mediaProjectionLauncher; public interface CaptureCallback { void onCaptureComplete(Bitmap bitmap); @@ -51,9 +52,22 @@ public ScreenCaptureManager(Context context) { appContext.getSystemService(Context.MEDIA_PROJECTION_SERVICE); } + /** + * Initialize the activity result launcher. This must be called in the activity's onCreate method + * before any fragments are created or the activity is started. + * + * @param activity The FragmentActivity to register the launcher with + */ + public void initialize(FragmentActivity activity) { + mediaProjectionLauncher = activity.registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + this::handleActivityResult + ); + } + /** * Request screen capture permission and start the capturing process. - * This needs to be called from an Activity. + * This needs to be called from an Activity after initialize() has been called. * * @param activity Activity to request permission from * @param callback Callback to receive the captured screenshot or failure @@ -63,6 +77,10 @@ public void requestCapturePermission(Activity activity, CaptureCallback callback return; } + if (mediaProjectionLauncher == null) { + throw new IllegalStateException("ScreenCaptureManager must be initialized before requesting permission. Call initialize() in onCreate()."); + } + this.captureCallback = callback; // Get screen metrics @@ -75,42 +93,43 @@ public void requestCapturePermission(Activity activity, CaptureCallback callback screenHeight = metrics.heightPixels; screenDensity = metrics.densityDpi; - // Request permission + // Request permission using the modern Activity Result API Intent intent = projectionManager.createScreenCaptureIntent(); - activity.startActivityForResult(intent, REQUEST_CODE_MEDIA_PROJECTION); + mediaProjectionLauncher.launch(intent); } /** - * Handle activity result from permission request. - * This should be called from the activity's onActivityResult method. + * Handle activity result from permission request using the modern Activity Result API. * - * @param requestCode Request code from onActivityResult - * @param resultCode Result code from onActivityResult - * @param data Intent data from onActivityResult - * @return true if handled by this manager, false otherwise + * @param result ActivityResult from the launcher */ - public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode != REQUEST_CODE_MEDIA_PROJECTION) { - return false; - } - - if (resultCode == Activity.RESULT_OK && data != null) { + private void handleActivityResult(ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { // Start the foreground service before creating MediaProjection ScreenCaptureService.start(appContext); // Wait briefly to ensure the service is fully started new Handler().postDelayed(() -> { - startCapture(resultCode, data); + startCapture(result.getResultCode(), result.getData()); }, 100); // Small delay to ensure service starts - return true; } else { if (captureCallback != null) { captureCallback.onCaptureFailed(); } - return true; } } + /** + * @deprecated Use the modern Activity Result API instead. This method is kept for backwards compatibility + * but may not work reliably on newer Android versions. + */ + @Deprecated + public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { + // This is kept for backwards compatibility but should not be used + // The modern approach using ActivityResultLauncher is more reliable + return false; + } + private final MediaProjection.Callback mediaProjectionCallback = new MediaProjection.Callback() { @Override public void onStop() { diff --git a/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java b/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java index b4bb76b..7b603ce 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java @@ -32,6 +32,7 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; +import androidx.fragment.app.FragmentActivity; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.jraska.falcon.Falcon; @@ -175,6 +176,18 @@ public void setUseMediaProjection(boolean useMediaProjection) { this.useMediaProjection = useMediaProjection; } + /** + * Initialize the ScreenCaptureManager for MediaProjection API. + * This must be called from the host activity's onCreate method before any screen capture operations. + * + * @param activity The FragmentActivity that will handle the MediaProjection permission flow + */ + public void initializeScreenCapture(FragmentActivity activity) { + if (screenCaptureManager != null) { + screenCaptureManager.initialize(activity); + } + } + /** * Handle activity result for MediaProjection permission request. * This must be called from the host activity's onActivityResult method. From 3271a3ce278ca53e8e5d15574c2b27137f3f3ecd Mon Sep 17 00:00:00 2001 From: Advait Mahashabde Date: Sun, 3 Aug 2025 21:51:16 -0700 Subject: [PATCH 2/2] Few more fixes --- .../main/java/com/linkedin/android/shaky/LifecycleCallbacks.java | 1 - .../java/com/linkedin/android/shaky/ScreenCaptureManager.java | 1 - 2 files changed, 2 deletions(-) diff --git a/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java b/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java index b73d2a4..52d6d13 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/LifecycleCallbacks.java @@ -19,7 +19,6 @@ import android.app.Application; import android.os.Bundle; import androidx.annotation.NonNull; -import androidx.fragment.app.FragmentActivity; class LifecycleCallbacks implements Application.ActivityLifecycleCallbacks { diff --git a/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java b/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java index de08379..70466c1 100644 --- a/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java +++ b/shaky/src/main/java/com/linkedin/android/shaky/ScreenCaptureManager.java @@ -16,7 +16,6 @@ import android.view.WindowManager; import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.fragment.app.FragmentActivity;