Skip to content

Commit f686c2f

Browse files
authored
Merge 9cd810d into 6c698c0
2 parents 6c698c0 + 9cd810d commit f686c2f

File tree

3 files changed

+83
-7
lines changed

3 files changed

+83
-7
lines changed

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionLifecycleNotifier.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,33 @@ <T, A extends Activity> Task<T> applyToNullableForegroundActivity(
160160
});
161161
}
162162

163+
/**
164+
* Apply a function to a foreground activity, when one is available, returning a {@link Task} that
165+
* will complete with the result of the Task returned by that function.
166+
*
167+
* <p>If the foreground activity is of type {@code classToIgnore}, the previously active activity
168+
* will be passed to the function, which may be null if there was no previously active activity or
169+
* the activity has been destroyed.
170+
*
171+
* <p>The continuation function will be called immediately once the activity is available. This
172+
* may be on the main thread or the calling thread, depending on whether or not there is already a
173+
* foreground activity available when this method is called.
174+
*/
175+
<T, A extends Activity> Task<T> applyToNullableForegroundActivityTask(
176+
Class<A> classToIgnore, SuccessContinuation<Activity, T> continuation) {
177+
return getForegroundActivity(classToIgnore)
178+
.onSuccessTask(
179+
// Use direct executor to ensure the consumer is called while Activity is in foreground
180+
DIRECT_EXECUTOR,
181+
activity -> {
182+
try {
183+
return continuation.then(activity);
184+
} catch (Throwable t) {
185+
return Tasks.forException(FirebaseAppDistributionExceptions.wrap(t));
186+
}
187+
});
188+
}
189+
163190
/** A version of {@link #applyToForegroundActivity} that does not produce a value. */
164191
Task<Void> consumeForegroundActivity(ActivityConsumer consumer) {
165192
return getForegroundActivity()

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionRegistrar.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.firebase.appdistribution.impl;
1616

17+
import android.annotation.SuppressLint;
1718
import android.app.Application;
1819
import android.content.Context;
1920
import androidx.annotation.Keep;
@@ -82,6 +83,8 @@ private FeedbackSender buildFeedbackSender(
8283
return new FeedbackSender(testerApiClient, blockingExecutor);
8384
}
8485

86+
// TODO(b/258264924): Migrate to go/firebase-android-executors
87+
@SuppressLint("ThreadPoolCreation")
8588
private FirebaseAppDistribution buildFirebaseAppDistribution(
8689
ComponentContainer container, Executor blockingExecutor) {
8790
FirebaseApp firebaseApp = container.get(FirebaseApp.class);

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/ScreenshotTaker.java

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@
1414

1515
package com.google.firebase.appdistribution.impl;
1616

17+
import android.annotation.SuppressLint;
18+
import android.annotation.TargetApi;
19+
import android.app.Activity;
1720
import android.content.Context;
1821
import android.graphics.Bitmap;
1922
import android.graphics.Canvas;
2023
import android.net.Uri;
24+
import android.os.Build;
25+
import android.os.Handler;
26+
import android.view.PixelCopy;
2127
import android.view.View;
2228
import androidx.annotation.Nullable;
2329
import androidx.annotation.VisibleForTesting;
2430
import com.google.android.gms.tasks.Task;
31+
import com.google.android.gms.tasks.TaskCompletionSource;
2532
import com.google.android.gms.tasks.Tasks;
2633
import com.google.firebase.FirebaseApp;
2734
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
@@ -42,6 +49,8 @@ class ScreenshotTaker {
4249
private final FirebaseAppDistributionLifecycleNotifier lifecycleNotifier;
4350
private final Executor taskExecutor;
4451

52+
// TODO(b/258264924): Migrate to go/firebase-android-executors
53+
@SuppressLint("ThreadPoolCreation")
4554
ScreenshotTaker(
4655
FirebaseApp firebaseApp, FirebaseAppDistributionLifecycleNotifier lifecycleNotifier) {
4756
this(firebaseApp, lifecycleNotifier, Executors.newSingleThreadExecutor());
@@ -81,7 +90,7 @@ Task<Void> deleteScreenshot() {
8190

8291
@VisibleForTesting
8392
Task<Bitmap> captureScreenshot() {
84-
return lifecycleNotifier.applyToNullableForegroundActivity(
93+
return lifecycleNotifier.applyToNullableForegroundActivityTask(
8594
// Ignore TakeScreenshotAndStartFeedbackActivity class if it's the current activity
8695
TakeScreenshotAndStartFeedbackActivity.class,
8796
activity -> {
@@ -93,19 +102,56 @@ Task<Bitmap> captureScreenshot() {
93102
// We only take the screenshot here because this will be called on the main thread, so we
94103
// want to do as little work as possible
95104
try {
96-
View view = activity.getWindow().getDecorView().getRootView();
97-
Bitmap bitmap =
98-
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);
99-
Canvas canvas = new Canvas(bitmap);
100-
view.draw(canvas);
101-
return bitmap;
105+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
106+
return captureScreenshotOreo(activity);
107+
} else {
108+
return captureScreenshotLegacy(activity);
109+
}
102110
} catch (Exception | OutOfMemoryError e) {
103111
throw new FirebaseAppDistributionException(
104112
"Failed to take screenshot", Status.UNKNOWN, e);
105113
}
106114
});
107115
}
108116

117+
private static Bitmap getBitmapForScreenshot(Activity activity) {
118+
View view = activity.getWindow().getDecorView().getRootView();
119+
return Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);
120+
}
121+
122+
private static Task<Bitmap> captureScreenshotLegacy(Activity activity) {
123+
Bitmap bitmap = getBitmapForScreenshot(activity);
124+
Canvas canvas = new Canvas(bitmap);
125+
activity.getWindow().getDecorView().getRootView().draw(canvas);
126+
return Tasks.forResult(bitmap);
127+
}
128+
129+
@TargetApi(Build.VERSION_CODES.O)
130+
private static Task<Bitmap> captureScreenshotOreo(Activity activity) {
131+
Bitmap bitmap = getBitmapForScreenshot(activity);
132+
TaskCompletionSource<Bitmap> taskCompletionSource = new TaskCompletionSource<>();
133+
try {
134+
// PixelCopy can handle Bitmaps with Bitmap.Config.HARDWARE
135+
PixelCopy.request(
136+
activity.getWindow(),
137+
bitmap,
138+
result -> {
139+
if (result == PixelCopy.SUCCESS) {
140+
taskCompletionSource.setResult(bitmap);
141+
} else {
142+
taskCompletionSource.setException(
143+
new FirebaseAppDistributionException(
144+
String.format("PixelCopy request failed: %s", result), Status.UNKNOWN));
145+
}
146+
},
147+
new Handler());
148+
} catch (IllegalArgumentException e) {
149+
taskCompletionSource.setException(
150+
new FirebaseAppDistributionException("Bad PixelCopy request", Status.UNKNOWN, e));
151+
}
152+
return taskCompletionSource.getTask();
153+
}
154+
109155
private Task<Uri> writeToFile(@Nullable Bitmap bitmap) {
110156
if (bitmap == null) {
111157
return Tasks.forResult(null);

0 commit comments

Comments
 (0)