Snap for 5948990 from 4d4f96d6a1e0e9fa5f7712c2f37cba0f2e77978c to qt-qpr1-aml-release
Change-Id: I32c5a2449def4052eaf050ec9b567dd8c12b80da
diff --git a/src/android/car/cluster/ActivityMonitor.java b/src/android/car/cluster/ActivityMonitor.java
index e7fb89c..28c8147 100644
--- a/src/android/car/cluster/ActivityMonitor.java
+++ b/src/android/car/cluster/ActivityMonitor.java
@@ -16,6 +16,7 @@
package android.car.cluster;
import android.annotation.Nullable;
+import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
@@ -23,6 +24,7 @@
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@@ -35,6 +37,12 @@
/**
* Top activity monitor, allows listeners to be notified when a new activity comes to the foreground
* on a particular device.
+ *
+ * As a sanity check {@link #notifyTopActivities} is handed to the UI thread because it is triggered
+ * by {@link #mProcessObserver} and {@link #mTaskStackListener}, which may be called by background
+ * threads.
+ *
+ * {@link #start} and {@link #stop} should be called only by the UI thread to prevent possible NPEs.
*/
public class ActivityMonitor {
private static final String TAG = "Cluster.ActivityMonitor";
@@ -54,11 +62,17 @@
private final Map<Integer, Set<ActivityListener>> mListeners = new HashMap<>();
private final Handler mHandler = new Handler();
private final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
+ /**
+ * Note: This function may sometimes be called from a background thread
+ */
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
notifyTopActivities();
}
+ /**
+ * Note: This function may sometimes be called from a background thread
+ */
@Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { }
@@ -68,6 +82,9 @@
}
};
private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+ /**
+ * Note: This function may sometimes be called from a background thread
+ */
@Override
public void onTaskStackChanged() {
Log.i(TAG, "onTaskStackChanged");
@@ -79,7 +96,7 @@
* Registers a new listener to receive activity updates on a particular display
*
* @param displayId identifier of the display to monitor
- * @param listener listener to be notified
+ * @param listener listener to be notified
*/
public void addListener(int displayId, ActivityListener listener) {
mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).add(listener);
@@ -94,7 +111,10 @@
/**
* Starts monitoring activity changes. {@link #stop()} should be invoked to release resources.
+ *
+ * This method should be called on the UI thread. Otherwise, runtime exceptions may occur.
*/
+ @UiThread
public void start() {
mActivityManager = ActivityManager.getService();
// Monitoring both listeners are necessary as there are cases where one listener cannot
@@ -111,11 +131,17 @@
/**
* Stops monitoring activity changes. Should be invoked when this monitor is not longer used.
+ *
+ * This method should be called on the UI thread. Otherwise, runtime exceptions may occur.
*/
+ @UiThread
public void stop() {
if (mActivityManager == null) {
return;
}
+ if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
+ Log.w(TAG, "stop() is called on non-UI thread. May cause NPE");
+ }
try {
mActivityManager.unregisterProcessObserver(mProcessObserver);
mActivityManager.unregisterTaskStackListener(mTaskStackListener);
@@ -126,12 +152,18 @@
}
/**
- * Notifies listeners on changes of top activities. {@link ActivityManager} might trigger
- * updates on threads different than UI.
+ * Notifies listeners on changes of top activities.
+ *
+ * Note: This method may sometimes be called by background threads, so it is synchronized on
+ * the UI thread with mHandler.post()
*/
private void notifyTopActivities() {
mHandler.post(() -> {
try {
+ // return if the activity monitor is no longer used
+ if (mActivityManager == null) {
+ return;
+ }
List<StackInfo> infos = mActivityManager.getAllStackInfos();
for (StackInfo info : infos) {
Set<ActivityListener> listeners = mListeners.get(info.displayId);
diff --git a/src/android/car/cluster/ImageResolver.java b/src/android/car/cluster/ImageResolver.java
index 45fcf31..2e1ada9 100644
--- a/src/android/car/cluster/ImageResolver.java
+++ b/src/android/car/cluster/ImageResolver.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.car.cluster.navigation.NavigationState.ImageReference;
-import android.car.cluster.renderer.InvalidSizeException;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
@@ -45,7 +44,12 @@
/**
* Returns a {@link Bitmap} given a request Uri and dimensions
*/
- Bitmap getBitmap(Uri uri, int width, int height) throws InvalidSizeException;
+ Bitmap getBitmap(Uri uri, int width, int height);
+
+ /**
+ * Returns a {@link Bitmap} given a request Uri, dimensions, and offLanesAlpha value
+ */
+ Bitmap getBitmap(Uri uri, int width, int height, float offLanesAlpha);
}
/**
@@ -65,6 +69,22 @@
*/
@NonNull
public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height) {
+ return getBitmap(img, width, height, 1f);
+ }
+
+ /**
+ * Returns a {@link CompletableFuture} that provides a bitmap from a {@link ImageReference}.
+ * This image would fit inside the provided size. Either width, height or both should be greater
+ * than 0.
+ *
+ * @param width required width, or 0 if width is flexible based on height.
+ * @param height required height, or 0 if height is flexible based on width.
+ * @param offLanesAlpha opacity value for off lane guidance images. Only applies to lane
+ * guidance images. 0 (transparent) <= offLanesAlpha <= 1 (opaque).
+ */
+ @NonNull
+ public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height,
+ float offLanesAlpha) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, String.format("Requesting image %s (width: %d, height: %d)",
img.getContentUri(), width, height));
@@ -81,9 +101,9 @@
Uri uri = Uri.parse(img.getContentUri());
Bitmap bitmap = null;
try {
- bitmap = mFetcher.getBitmap(uri, adjusted.x, adjusted.y);
- } catch (InvalidSizeException e) {
- Log.e(TAG, "Bitmap must have positive width and height");
+ bitmap = mFetcher.getBitmap(uri, adjusted.x, adjusted.y, offLanesAlpha);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.getMessage());
}
if (bitmap == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -111,12 +131,28 @@
@NonNull
public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
@NonNull List<ImageReference> imgs, int width, int height) {
+ return getBitmaps(imgs, width, height, 1f);
+ }
+
+ /**
+ * Same as {@link #getBitmap(ImageReference, int, int)} but it works on a list of images. The
+ * returning {@link CompletableFuture} will contain a map from each {@link ImageReference} to
+ * its bitmap. If any image fails to be fetched, the whole future completes exceptionally.
+ *
+ * @param width required width, or 0 if width is flexible based on height.
+ * @param height required height, or 0 if height is flexible based on width.
+ * @param offLanesAlpha opacity value for off lane guidance images. Only applies to lane
+ * guidance images. 0 (transparent) <= offLanesAlpha <= 1 (opaque).
+ */
+ @NonNull
+ public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
+ @NonNull List<ImageReference> imgs, int width, int height, float offLanesAlpha) {
CompletableFuture<Map<ImageReference, Bitmap>> future = new CompletableFuture<>();
Map<ImageReference, CompletableFuture<Bitmap>> bitmapFutures = imgs.stream().collect(
Collectors.toMap(
img -> img,
- img -> getBitmap(img, width, height)));
+ img -> getBitmap(img, width, height, offLanesAlpha)));
CompletableFuture.allOf(bitmapFutures.values().toArray(new CompletableFuture[0]))
.thenAccept(v -> {
diff --git a/tests/robotests/src/android/car/cluster/ImageResolverTest.java b/tests/robotests/src/android/car/cluster/ImageResolverTest.java
index 0c05390..9b39659 100644
--- a/tests/robotests/src/android/car/cluster/ImageResolverTest.java
+++ b/tests/robotests/src/android/car/cluster/ImageResolverTest.java
@@ -17,7 +17,9 @@
import static org.junit.Assert.assertEquals;
+import android.graphics.Bitmap;
import android.graphics.Point;
+import android.net.Uri;
import org.junit.Before;
import org.junit.Test;
@@ -32,7 +34,17 @@
@Before
public void setup() {
- mImageResolver = new ImageResolver((uri, w, h) -> null);
+ mImageResolver = new ImageResolver(new ImageResolver.BitmapFetcher() {
+ @Override
+ public Bitmap getBitmap(Uri uri, int width, int height) {
+ return null;
+ }
+
+ @Override
+ public Bitmap getBitmap(Uri uri, int width, int height, float offLanesAlpha) {
+ return null;
+ }
+ });
}
@Test