Merge "Avoid applying window config from updating persistent config" into sc-v2-dev
diff --git a/core/api/current.txt b/core/api/current.txt
index ef886e4..94d650d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -99,6 +99,7 @@
field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
field public static final String INTERNET = "android.permission.INTERNET";
field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
+ field public static final String LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK";
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
@@ -35226,6 +35227,7 @@
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
+ field public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK = "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK";
field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO";
field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
@@ -35266,6 +35268,9 @@
field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE";
field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
+ field public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI";
+ field public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_PENDING_INTENT = "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_PENDING_INTENT";
+ field public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY";
field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST";
field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2ca073b..0d3dd7c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -26,6 +26,7 @@
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
+ field public static final String ALLOW_PLACE_IN_TWO_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS";
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 7c42dc0..d48d562 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -850,26 +850,6 @@
}
/**
- * Checks if the specified user has enrollments in any of the specified sensors.
- * @hide
- */
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors) {
- if (mService == null) {
- Slog.w(TAG, "hasEnrolledTemplatesForAnySensor: no fingerprint service");
- return false;
- }
-
- try {
- return mService.hasEnrolledTemplatesForAnySensor(userId, sensors,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/fingerprint/FingerprintStateListener.java b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
index 6e607a2..cf914c5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintStateListener.java
+++ b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
@@ -49,5 +49,10 @@
* Defines behavior in response to state update
* @param newState new state of fingerprint sensor
*/
- public abstract void onStateChanged(@FingerprintStateListener.State int newState);
+ public void onStateChanged(@FingerprintStateListener.State int newState) {};
+
+ /**
+ * Invoked when enrollment state changes for the specified user
+ */
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {};
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 4774827..de94b2f 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -120,9 +120,6 @@
// Determine if a user has at least one enrolled fingerprint.
boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName);
- // Determine if a user has at least one enrolled fingerprint in any of the specified sensors
- boolean hasEnrolledTemplatesForAnySensor(int userId, in List<FingerprintSensorPropertiesInternal> sensors, String opPackageName);
-
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int sensorId, int userId);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
index 56dba7e..1aa6fa1 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
@@ -24,4 +24,5 @@
*/
oneway interface IFingerprintStateListener {
void onStateChanged(int newState);
+ void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5395d8d..ab13e2e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -28,12 +28,14 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.app.Activity;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.Application;
import android.app.AutomaticZenRule;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.SearchManager;
import android.app.WallpaperManager;
import android.compat.annotation.UnsupportedAppUsage;
@@ -16942,6 +16944,54 @@
"android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
/**
+ * Activity Action: For system or preinstalled apps to show their {@link Activity} in 2-pane
+ * mode in Settings app on large screen devices.
+ * <p>
+ * Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI} must be included to
+ * specify the intent for the activity which will be displayed in 2-pane mode in Settings app.
+ * It's an intent URI string from {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
+ *
+ * Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY} must be included to
+ * specify a key that indicates the menu item which will be highlighted on settings home menu.
+ *
+ * Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_PENDING_INTENT} is optional. Apps
+ * can use the {@link PendingIntent} extra to launch into its private {@link Activity}.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK =
+ "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK";
+
+ /**
+ * Activity Extra: Specify the intent for the {@link Activity} which will be displayed in 2-pane
+ * mode in Settings app. It's an intent URI string from
+ * {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
+ * <p>
+ * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}.
+ */
+ public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI =
+ "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI";
+
+ /**
+ * Activity Extra: Specify a key that indicates the menu item which should be highlighted on
+ * settings home menu.
+ * <p>
+ * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}.
+ */
+ public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY =
+ "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY";
+
+ /**
+ * Activity Extra: Apps can use the {@link PendingIntent} extra to launch into its private
+ * {@link Activity}.
+ * <p>
+ * This is an optional extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}.
+ */
+ public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_PENDING_INTENT =
+ "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_PENDING_INTENT";
+
+ /**
* Performs a strict and comprehensive check of whether a calling package is allowed to
* write/modify system settings, as the condition differs for pre-M, M+, and
* privileged/preinstalled apps. If the provided uid does not match the
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index acf20d7..f14294e 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -59,6 +59,7 @@
import java.time.Duration;
import java.time.Instant;
+import java.util.function.Consumer;
/**
* <p>The view which allows an activity to customize its splash screen exit animation.</p>
@@ -144,6 +145,7 @@
private Bitmap mParceledBrandingBitmap;
private Instant mIconAnimationStart;
private Duration mIconAnimationDuration;
+ private Consumer<Runnable> mUiThreadInitTask;
public Builder(@NonNull Context context) {
mContext = context;
@@ -232,6 +234,15 @@
}
/**
+ * Set the Runnable that can receive the task which should be executed on UI thread.
+ * @param uiThreadInitTask
+ */
+ public Builder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+ mUiThreadInitTask = uiThreadInitTask;
+ return this;
+ }
+
+ /**
* Set the Drawable object and size for the branding view.
*/
public Builder setBrandingDrawable(@Nullable Drawable branding, int width, int height) {
@@ -262,7 +273,11 @@
// center icon
if (mIconDrawable instanceof SplashScreenView.IconAnimateListener
|| mSurfacePackage != null) {
- view.mIconView = createSurfaceView(view);
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.accept(() -> view.mIconView = createSurfaceView(view));
+ } else {
+ view.mIconView = createSurfaceView(view);
+ }
view.initIconAnimation(mIconDrawable,
mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0);
view.mIconAnimationStart = mIconAnimationStart;
@@ -316,7 +331,9 @@
}
private SurfaceView createSurfaceView(@NonNull SplashScreenView view) {
- final SurfaceView surfaceView = new SurfaceView(view.getContext());
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#createSurfaceView");
+ final Context viewContext = view.getContext();
+ final SurfaceView surfaceView = new SurfaceView(viewContext);
surfaceView.setPadding(0, 0, 0, 0);
surfaceView.setBackground(mIconBackground);
if (mSurfacePackage == null) {
@@ -326,10 +343,10 @@
+ Thread.currentThread().getId());
}
- SurfaceControlViewHost viewHost = new SurfaceControlViewHost(mContext,
- mContext.getDisplay(),
+ SurfaceControlViewHost viewHost = new SurfaceControlViewHost(viewContext,
+ viewContext.getDisplay(),
surfaceView.getHostToken());
- ImageView imageView = new ImageView(mContext);
+ ImageView imageView = new ImageView(viewContext);
imageView.setBackground(mIconDrawable);
viewHost.setView(imageView, mIconSize, mIconSize);
SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
@@ -360,6 +377,7 @@
view.addView(surfaceView);
view.mSurfaceView = surfaceView;
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return surfaceView;
}
}
@@ -531,17 +549,14 @@
private void releaseAnimationSurfaceHost() {
if (mSurfaceHost != null && !mIsCopied) {
- final SurfaceControlViewHost finalSurfaceHost = mSurfaceHost;
+ if (DEBUG) {
+ Log.d(TAG,
+ "Shell removed splash screen."
+ + " Releasing SurfaceControlViewHost on thread #"
+ + Thread.currentThread().getId());
+ }
+ releaseIconHost(mSurfaceHost);
mSurfaceHost = null;
- finalSurfaceHost.getView().post(() -> {
- if (DEBUG) {
- Log.d(TAG,
- "Shell removed splash screen."
- + " Releasing SurfaceControlViewHost on thread #"
- + Thread.currentThread().getId());
- }
- finalSurfaceHost.release();
- });
} else if (mSurfacePackage != null && mSurfaceHost == null) {
mSurfacePackage = null;
mClientCallback.sendResult(null);
@@ -549,6 +564,18 @@
}
/**
+ * Release the host which hold the SurfaceView of the icon.
+ * @hide
+ */
+ public static void releaseIconHost(SurfaceControlViewHost host) {
+ final Drawable background = host.getView().getBackground();
+ if (background instanceof SplashScreenView.IconAnimateListener) {
+ ((SplashScreenView.IconAnimateListener) background).stopAnimation();
+ }
+ host.release();
+ }
+
+ /**
* Called when this view is attached to an activity. This also makes SystemUI colors
* transparent so the content of splash screen view can draw fully.
*
@@ -639,6 +666,11 @@
* @return true if this drawable object can also be animated and it can be played now.
*/
boolean prepareAnimate(long duration, Runnable startListener);
+
+ /**
+ * Stop animation.
+ */
+ void stopAnimation();
}
/**
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index aa7142e..c863292 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -51,6 +51,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
/**
* A class that allows the app to get the frame metrics from HardwareRendererObserver.
@@ -108,6 +109,7 @@
private boolean mCancelled = false;
private FrameTrackerListener mListener;
private boolean mTracingStarted = false;
+ private Runnable mWaitForFinishTimedOut;
private static class JankInfo {
long frameVsyncId;
@@ -174,52 +176,51 @@
// If the surface isn't valid yet, wait until it's created.
if (mViewRoot.getSurfaceControl().isValid()) {
mSurfaceControl = mViewRoot.getSurfaceControl();
- mSurfaceChangedCallback = null;
- } else {
- mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
- @Override
- public void surfaceCreated(SurfaceControl.Transaction t) {
- synchronized (FrameTracker.this) {
- if (mSurfaceControl == null) {
- mSurfaceControl = mViewRoot.getSurfaceControl();
- if (mBeginVsyncId != INVALID_ID) {
- mSurfaceControlWrapper.addJankStatsListener(
- FrameTracker.this, mSurfaceControl);
- postTraceStartMarker();
- }
+ }
+
+ mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ synchronized (FrameTracker.this) {
+ if (mSurfaceControl == null) {
+ mSurfaceControl = mViewRoot.getSurfaceControl();
+ if (mBeginVsyncId != INVALID_ID) {
+ mSurfaceControlWrapper.addJankStatsListener(
+ FrameTracker.this, mSurfaceControl);
+ postTraceStartMarker();
}
}
}
+ }
- @Override
- public void surfaceReplaced(SurfaceControl.Transaction t) {
- }
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
- @Override
- public void surfaceDestroyed() {
+ @Override
+ public void surfaceDestroyed() {
- // Wait a while to give the system a chance for the remaining
- // frames to arrive, then force finish the session.
- mHandler.postDelayed(() -> {
- synchronized (FrameTracker.this) {
- if (DEBUG) {
- Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
- + ", finalized=" + mMetricsFinalized
- + ", info=" + mJankInfos.size()
- + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId);
- }
- if (!mMetricsFinalized) {
- end(REASON_END_SURFACE_DESTROYED);
- finish(mJankInfos.size() - 1);
- }
+ // Wait a while to give the system a chance for the remaining
+ // frames to arrive, then force finish the session.
+ mHandler.postDelayed(() -> {
+ synchronized (FrameTracker.this) {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
+ + ", finalized=" + mMetricsFinalized
+ + ", info=" + mJankInfos.size()
+ + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId);
}
- }, 50);
- }
- };
- // This callback has a reference to FrameTracker,
- // remember to remove it to avoid leakage.
- mViewRoot.addSurfaceChangedCallback(mSurfaceChangedCallback);
- }
+ if (!mMetricsFinalized) {
+ end(REASON_END_SURFACE_DESTROYED);
+ finish(mJankInfos.size() - 1);
+ }
+ }
+ }, 50);
+ }
+ };
+ // This callback has a reference to FrameTracker,
+ // remember to remove it to avoid leakage.
+ mViewRoot.addSurfaceChangedCallback(mSurfaceChangedCallback);
}
}
@@ -283,10 +284,17 @@
if (mListener != null) {
mListener.onCujEvents(mSession, ACTION_SESSION_END);
}
+
+ // We don't remove observer here,
+ // will remove it when all the frame metrics in this duration are called back.
+ // See onFrameMetricsAvailable for the logic of removing the observer.
+ // Waiting at most 10 seconds for all callbacks to finish.
+ mWaitForFinishTimedOut = () -> {
+ Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
+ finish(mJankInfos.size() - 1);
+ };
+ mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
}
- // We don't remove observer here,
- // will remove it when all the frame metrics in this duration are called back.
- // See onFrameMetricsAvailable for the logic of removing the observer.
}
/**
@@ -423,7 +431,8 @@
}
private void finish(int indexOnOrAfterEnd) {
-
+ mHandler.removeCallbacks(mWaitForFinishTimedOut);
+ mWaitForFinishTimedOut = null;
mMetricsFinalized = true;
// The tracing has been ended, remove the observer, see if need to trigger perfetto.
@@ -509,7 +518,7 @@
}
}
if (DEBUG) {
- Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName()
+ Log.i(TAG, "finish: CUJ=" + mSession.getName()
+ " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
+ " totalFrames=" + totalFramesCount
+ " missedAppFrames=" + missedAppFramesCount
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 0f153bc..db019a67 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -82,6 +82,8 @@
Consts.TAG_WM),
WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
+ WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1704452..e53d379 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4862,6 +4862,18 @@
<permission android:name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"
android:protectionLevel="signature|privileged" />
+ <!-- An application needs this permission for
+ {@link android.provider.Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK} to show its
+ {@link android.app.Activity} in 2-pane of Settings app. -->
+ <permission android:name="android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- @SystemApi {@link android.app.Activity} should require this permission to ensure that only
+ the settings app can embed it in a 2-pane window.
+ @hide -->
+ <permission android:name="android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows applications to set a live wallpaper.
@hide XXX Change to signature once the picker is moved to its
own apk as Ghod Intended. -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 6a93761..0d7225b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -211,6 +211,12 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
+ "-1898316768": {
+ "message": "Unable to retrieve window container to start layer mirroring for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-1895337367": {
"message": "Delete root task display=%d winMode=%d",
"level": "VERBOSE",
@@ -1093,6 +1099,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-904499590": {
+ "message": "Provided surface for layer mirroring on display %d is not present, so do not update the surface",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-883738232": {
"message": "Adding more than one toast window for UID at a time.",
"level": "WARN",
@@ -1303,6 +1315,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-663411559": {
+ "message": "Going ahead with updating layer mirroring for display %d to new bounds %s and\/or orientation %d.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-655104359": {
"message": "Frontmost changed immersion: %s",
"level": "DEBUG",
@@ -1561,6 +1579,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-384564722": {
+ "message": "Unable to start layer mirroring for display %d since the surface is not available.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-381475323": {
"message": "DisplayContent: boot is waiting for window of type %d to be drawn",
"level": "DEBUG",
@@ -1633,6 +1657,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "-309399422": {
+ "message": "Display %d state is now (%d), so update layer mirroring?",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-302468788": {
"message": "Expected target rootTask=%s to be top most but found rootTask=%s",
"level": "WARN",
@@ -1699,6 +1729,12 @@
"group": "WM_DEBUG_WINDOW_MOVEMENT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-190034097": {
+ "message": "Unable to retrieve window container to update layer mirroring for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-177040661": {
"message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s",
"level": "DEBUG",
@@ -1795,6 +1831,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "-79877120": {
+ "message": "Display %d has content (%b) so disable layer mirroring",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-70719599": {
"message": "Unregister remote animations for organizer=%s uid=%d pid=%d",
"level": "VERBOSE",
@@ -2347,6 +2389,12 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
},
+ "504397469": {
+ "message": "Unable to update layer mirroring for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"508887531": {
"message": "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
@@ -3103,6 +3151,12 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1407569006": {
+ "message": "Display %d was already layer mirroring, so apply transformations if necessary",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"1417601133": {
"message": "Enqueueing ADD_STARTING",
"level": "VERBOSE",
@@ -3349,6 +3403,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1687376052": {
+ "message": "Display %d has no content and is on, so start layer mirroring for state %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"1689989893": {
"message": "SyncGroup %d: Set ready",
"level": "VERBOSE",
@@ -3753,6 +3813,9 @@
"WM_DEBUG_KEEP_SCREEN_ON": {
"tag": "WindowManager"
},
+ "WM_DEBUG_LAYER_MIRRORING": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_LOCKTASK": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
index 05c6792..12c273a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
@@ -117,6 +117,10 @@
}
container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo());
+ if (container.isFinished()) {
+ mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ updateCallbackIfNecessary();
+ }
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
index a9155cf..8503b9f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
@@ -138,7 +138,7 @@
return mInfo;
}
- void setInfo(@Nullable TaskFragmentInfo info) {
+ void setInfo(@NonNull TaskFragmentInfo info) {
mInfo = info;
if (mInfo == null || mPendingAppearedActivities.isEmpty()) {
return;
@@ -190,20 +190,30 @@
*/
void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
- if (mIsFinished) {
- return;
+ if (!mIsFinished) {
+ mIsFinished = true;
+ finishActivities(shouldFinishDependent, presenter, wct, controller);
}
- mIsFinished = true;
- // Finish own activities
- for (Activity activity : collectActivities()) {
- activity.finish();
+ if (mInfo == null) {
+ // Defer removal the container and wait until TaskFragment appeared.
+ return;
}
// Cleanup the visuals
presenter.deleteTaskFragment(wct, getTaskFragmentToken());
// Cleanup the records
controller.removeContainer(this);
+ // Clean up task fragment information
+ mInfo = null;
+ }
+
+ private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
+ @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
+ // Finish own activities
+ for (Activity activity : collectActivities()) {
+ activity.finish();
+ }
if (!shouldFinishDependent) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 95b80df..9dafefe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -628,8 +628,11 @@
mAddedToWindowManager = true;
mBubbleData.getOverflow().initialize(this);
mWindowManager.addView(mStackView, mWmLayoutParams);
- // Position info is dependent on us being attached to a window
- mBubblePositioner.update();
+ mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ mBubblePositioner.update();
+ mStackView.onDisplaySizeChanged();
+ return windowInsets;
+ });
} catch (IllegalStateException e) {
// This means the stack has already been added. This shouldn't happen...
e.printStackTrace();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 5a51eed..5bc6128 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1315,6 +1315,7 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mPositioner.update();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 81cad5a..754b8da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -305,6 +305,10 @@
mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
mSplitWindowManager.setResizingSplits(false);
updateBounds(mDividePosition);
+ mWinToken1 = null;
+ mWinToken2 = null;
+ mWinBounds1.setEmpty();
+ mWinBounds2.setEmpty();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 3512a0c..da91c1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -113,10 +113,20 @@
/** @return {@code true} if this listener contains the currently focused task. */
boolean isFocused() {
- if (mRootTaskInfo.isFocused) return true;
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- if (mChildrenTaskInfo.valueAt(i).isFocused) return true;
+ if (mRootTaskInfo == null) {
+ return false;
}
+
+ if (mRootTaskInfo.isFocused) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isFocused) {
+ return true;
+ }
+ }
+
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 2286598..8df7cbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -50,6 +50,7 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
+import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
import android.view.View;
import android.window.SplashScreenView;
@@ -137,12 +138,14 @@
* null if failed.
*/
void createContentView(Context context, @StartingWindowType int suggestType, ActivityInfo info,
- int taskId, Consumer<SplashScreenView> splashScreenViewConsumer) {
+ int taskId, Consumer<SplashScreenView> splashScreenViewConsumer,
+ Consumer<Runnable> uiThreadInitConsumer) {
mSplashscreenWorkerHandler.post(() -> {
SplashScreenView contentView;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView");
- contentView = makeSplashScreenContentView(context, info, suggestType);
+ contentView = makeSplashScreenContentView(context, info, suggestType,
+ uiThreadInitConsumer);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RuntimeException e) {
Slog.w(TAG, "failed creating starting window content at taskId: "
@@ -238,7 +241,7 @@
}
private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai,
- @StartingWindowType int suggestType) {
+ @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
updateDensity();
getWindowAttrs(context, mTmpAttrs);
@@ -253,6 +256,7 @@
.setWindowBGColor(themeBGColor)
.overlayDrawable(legacyDrawable)
.chooseStyle(suggestType)
+ .setUiThreadInitConsumer(uiThreadInitConsumer)
.build();
}
@@ -299,6 +303,11 @@
}
}
+ /** Creates the wrapper with system theme to avoid unexpected styles from app. */
+ ContextThemeWrapper createViewContextWrapper(Context appContext) {
+ return new ContextThemeWrapper(appContext, mContext.getTheme());
+ }
+
/** The configuration of the splash screen window. */
public static class SplashScreenWindowAttrs {
private int mWindowBgResId = 0;
@@ -318,6 +327,7 @@
private int mThemeColor;
private Drawable[] mFinalIconDrawables;
private int mFinalIconSize = mIconSize;
+ private Consumer<Runnable> mUiThreadInitTask;
StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
mContext = context;
@@ -339,6 +349,11 @@
return this;
}
+ StartingWindowViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+ mUiThreadInitTask = uiThreadInitTask;
+ return this;
+ }
+
SplashScreenView build() {
Drawable iconDrawable;
final int animationDuration;
@@ -385,7 +400,8 @@
animationDuration = 0;
}
- return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration);
+ return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration,
+ mUiThreadInitTask);
}
private class ShapeIconFactory extends BaseIconFactory {
@@ -463,7 +479,7 @@
}
private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
- int animationDuration) {
+ int animationDuration, Consumer<Runnable> uiThreadInitTask) {
Drawable foreground = null;
Drawable background = null;
if (iconDrawable != null) {
@@ -472,13 +488,15 @@
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
- final SplashScreenView.Builder builder = new SplashScreenView.Builder(mContext)
+ final ContextThemeWrapper wrapper = createViewContextWrapper(mContext);
+ final SplashScreenView.Builder builder = new SplashScreenView.Builder(wrapper)
.setBackgroundColor(mThemeColor)
.setOverlayDrawable(mOverlayDrawable)
.setIconSize(iconSize)
.setIconBackground(background)
.setCenterViewDrawable(foreground)
- .setAnimationDurationMillis(animationDuration);
+ .setAnimationDurationMillis(animationDuration)
+ .setUiThreadInitConsumer(uiThreadInitTask);
if (mSuggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
&& mTmpAttrs.mBrandingImage != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 951b97e..f0685a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -304,6 +304,13 @@
return true;
}
+ @Override
+ public void stopAnimation() {
+ if (mIconAnimator != null) {
+ mIconAnimator.end();
+ }
+ }
+
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable who) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 6c60bad..debe6d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -146,7 +146,7 @@
return mDisplayManager.getDisplay(displayId);
}
- private int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
+ int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
return splashScreenThemeResId != 0
? splashScreenThemeResId
: activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
@@ -174,7 +174,7 @@
final int displayId = taskInfo.displayId;
final int taskId = taskInfo.taskId;
- Context context = mContext;
+
// replace with the default theme if the application didn't set
final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
if (DEBUG_SPLASH_SCREEN) {
@@ -182,12 +182,16 @@
+ " theme=" + Integer.toHexString(theme) + " task=" + taskInfo.taskId
+ " suggestType=" + suggestType);
}
-
final Display display = getDisplay(displayId);
if (display == null) {
// Can't show splash screen on requested display, so skip showing at all.
return;
}
+ Context context = displayId == DEFAULT_DISPLAY
+ ? mContext : mContext.createDisplayContext(display);
+ if (context == null) {
+ return;
+ }
if (theme != context.getThemeResId()) {
try {
context = context.createPackageContextAsUser(activityInfo.packageName,
@@ -298,7 +302,8 @@
// Record whether create splash screen view success, notify to current thread after
// create splash screen view finished.
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
- final FrameLayout rootLayout = new FrameLayout(context);
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(context));
rootLayout.setPadding(0, 0, 0, 0);
rootLayout.setFitsSystemWindows(false);
final Runnable setViewSynchronized = () -> {
@@ -327,7 +332,7 @@
mSysuiProxy.requestTopUi(true, TAG);
}
mSplashscreenContentDrawer.createContentView(context, suggestType, activityInfo, taskId,
- viewSupplier::setView);
+ viewSupplier::setView, viewSupplier::setUiThreadInitTask);
try {
if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
// We use the splash screen worker thread to create SplashScreenView while adding
@@ -362,6 +367,7 @@
private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
private SplashScreenView mView;
private boolean mIsViewSet;
+ private Runnable mUiThreadInitTask;
void setView(SplashScreenView view) {
synchronized (this) {
mView = view;
@@ -370,6 +376,12 @@
}
}
+ void setUiThreadInitTask(Runnable initTask) {
+ synchronized (this) {
+ mUiThreadInitTask = initTask;
+ }
+ }
+
@Override
public @Nullable SplashScreenView get() {
synchronized (this) {
@@ -379,6 +391,10 @@
} catch (InterruptedException ignored) {
}
}
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.run();
+ mUiThreadInitTask = null;
+ }
return mView;
}
}
@@ -501,7 +517,7 @@
Slog.v(TAG, reason + "the splash screen. Releasing SurfaceControlViewHost for task:"
+ taskId);
}
- viewHost.getView().post(viewHost::release);
+ SplashScreenView.releaseIconHost(viewHost);
}
protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 160b367..2994e71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
@@ -85,7 +86,6 @@
static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
int mAddWindowForTask = 0;
- int mViewThemeResId;
TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
TransactionPool pool) {
@@ -97,7 +97,6 @@
WindowManager.LayoutParams params, int suggestType) {
// listen for addView
mAddWindowForTask = taskId;
- mViewThemeResId = view.getContext().getThemeResId();
// Do not wait for background color
return false;
}
@@ -167,12 +166,15 @@
final int taskId = 1;
final StartingWindowInfo windowInfo =
createWindowInfo(taskId, 0);
+ final int[] theme = new int[1];
+ doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod())
+ .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
- eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
+ verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ assertNotEquals(theme[0], 0);
}
@Test
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
index b52933d..05352c5 100644
--- a/packages/SystemUI/res/layout/internet_list_item.xml
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -70,7 +70,7 @@
android:id="@+id/wifi_summary"
android:textDirection="locale"
android:layout_width="wrap_content"
- android:layout_height="20dp"
+ android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:ellipsize="end"
android:textColor="?android:attr/textColorSecondary"
@@ -85,7 +85,7 @@
android:clickable="false"
android:layout_gravity="end|center_vertical">
<ImageView
- android:id="@+id/wifi_locked_icon"
+ android:id="@+id/wifi_end_icon"
android:layout_gravity="end|center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
diff --git a/packages/SystemUI/res/layout/smart_action_button.xml b/packages/SystemUI/res/layout/smart_action_button.xml
index 488be3a..4e5785d 100644
--- a/packages/SystemUI/res/layout/smart_action_button.xml
+++ b/packages/SystemUI/res/layout/smart_action_button.xml
@@ -29,8 +29,8 @@
android:textSize="@dimen/smart_reply_button_font_size"
android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
android:textColor="@color/smart_reply_button_text"
- android:paddingLeft="@dimen/smart_reply_button_action_padding_left"
- android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+ android:paddingStart="@dimen/smart_reply_button_action_padding_left"
+ android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
android:drawablePadding="@dimen/smart_action_button_icon_padding"
android:textStyle="normal"
android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/smart_reply_button.xml b/packages/SystemUI/res/layout/smart_reply_button.xml
index ddf16e0..b24362f 100644
--- a/packages/SystemUI/res/layout/smart_reply_button.xml
+++ b/packages/SystemUI/res/layout/smart_reply_button.xml
@@ -31,7 +31,7 @@
android:textSize="@dimen/smart_reply_button_font_size"
android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
android:textColor="@color/smart_reply_button_text"
- android:paddingLeft="@dimen/smart_reply_button_padding_horizontal"
- android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+ android:paddingStart="@dimen/smart_reply_button_padding_horizontal"
+ android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
android:textStyle="normal"
android:ellipsize="none"/>
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index b9ade02..19d029bf 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -568,7 +568,8 @@
}
}
- private static int getBoundPositionFromRotation(@BoundsPosition int pos, int rotation) {
+ @VisibleForTesting
+ static int getBoundPositionFromRotation(@BoundsPosition int pos, int rotation) {
return (pos - rotation) < 0
? pos - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
: pos - rotation;
@@ -963,7 +964,8 @@
}
}
- private boolean isTopRoundedCorner(@BoundsPosition int pos, int id) {
+ @VisibleForTesting
+ boolean isTopRoundedCorner(@BoundsPosition int pos, int id) {
switch (pos) {
case BOUNDS_POSITION_LEFT:
case BOUNDS_POSITION_RIGHT:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index bcc0530..0790af9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -42,6 +42,7 @@
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
@@ -49,6 +50,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -76,6 +78,9 @@
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
+ *
+ * Also coordinates biometric-related things, such as UDFPS, with
+ * {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@@ -115,6 +120,8 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
+ @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+
private class BiometricTaskStackListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
@@ -122,6 +129,21 @@
}
}
+ private final FingerprintStateListener mFingerprintStateListener =
+ new FingerprintStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ Log.d(TAG, "onEnrollmentsChanged, userId: " + userId
+ + ", sensorId: " + sensorId
+ + ", hasEnrollments: " + hasEnrollments);
+ for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) {
+ if (prop.sensorId == sensorId) {
+ mUdfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
+ };
+
@NonNull
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -436,6 +458,7 @@
mUdfpsControllerFactory = udfpsControllerFactory;
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
+ mUdfpsEnrolledForUser = new SparseBooleanArray();
mOrientationListener = new BiometricOrientationEventListener(context,
() -> {
onOrientationChanged();
@@ -474,6 +497,7 @@
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
mFingerprintAuthenticatorsRegisteredCallback);
+ mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener);
}
mTaskStackListener = new BiometricTaskStackListener();
@@ -673,7 +697,7 @@
return false;
}
- return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
+ return mUdfpsEnrolledForUser.get(userId);
}
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 7eedb3a..359f3a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -326,6 +326,7 @@
public void onAnimationAtStart() {
super.onAnimationAtStart();
mClockDateView.setFreezeSwitching(false);
+ mClockDateView.setVisibility(View.VISIBLE);
setSeparatorVisibility(mShowClockIconsSeparator);
// In QQS we never ignore RSSI.
mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index 4e897d9..99eb5b6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -16,15 +16,11 @@
package com.android.systemui.qs.tiles.dialog;
-import static com.android.wifitrackerlib.WifiEntry.SECURITY_NONE;
-import static com.android.wifitrackerlib.WifiEntry.SECURITY_OWE;
-
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.TextUtils;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,6 +32,7 @@
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.settingslib.wifi.WifiUtils;
import com.android.systemui.R;
@@ -114,10 +111,11 @@
final ImageView mWifiIcon;
final TextView mWifiTitleText;
final TextView mWifiSummaryText;
- final ImageView mWifiLockedIcon;
+ final ImageView mWifiEndIcon;
final Context mContext;
final InternetDialogController mInternetDialogController;
+ @VisibleForTesting
protected WifiUtils.InternetIconInjector mWifiIconInjector;
InternetViewHolder(View view, InternetDialogController internetDialogController) {
@@ -130,28 +128,25 @@
mWifiIcon = view.requireViewById(R.id.wifi_icon);
mWifiTitleText = view.requireViewById(R.id.wifi_title);
mWifiSummaryText = view.requireViewById(R.id.wifi_summary);
- mWifiLockedIcon = view.requireViewById(R.id.wifi_locked_icon);
+ mWifiEndIcon = view.requireViewById(R.id.wifi_end_icon);
mWifiIconInjector = mInternetDialogController.getWifiIconInjector();
}
- void onBind(WifiEntry wifiEntry) {
- int security = wifiEntry.getSecurity();
- try {
- mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry));
- if (isOpenNetwork(security)) {
- mWifiLockedIcon.setVisibility(View.GONE);
- } else {
- mWifiLockedIcon.setVisibility(View.VISIBLE);
- mWifiLockedIcon.setImageDrawable(
- mContext.getDrawable(R.drawable.ic_friction_lock_closed));
- }
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- }
-
+ void onBind(@NonNull WifiEntry wifiEntry) {
+ mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry));
setWifiNetworkLayout(wifiEntry.getTitle(),
Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY));
+ final int connectedState = wifiEntry.getConnectedState();
+ final int security = wifiEntry.getSecurity();
+ updateEndIcon(connectedState, security);
+
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ mWifiListLayout.setOnClickListener(
+ v -> mInternetDialogController.launchWifiNetworkDetailsSetting(
+ wifiEntry.getKey()));
+ return;
+ }
mWifiListLayout.setOnClickListener(v -> {
if (wifiEntry.shouldEditBeforeConnect()) {
final Intent intent = new Intent(ACTION_WIFI_DIALOG);
@@ -165,25 +160,17 @@
});
}
- /** Return true if this is an open network AccessPoint. */
- boolean isOpenNetwork(int security) {
- return security == SECURITY_NONE
- || security == SECURITY_OWE;
- }
-
void setWifiNetworkLayout(CharSequence title, CharSequence summary) {
- mWifiNetworkLayout.setVisibility(View.VISIBLE);
mWifiTitleText.setText(title);
if (TextUtils.isEmpty(summary)) {
mWifiSummaryText.setVisibility(View.GONE);
return;
- } else {
- mWifiSummaryText.setVisibility(View.VISIBLE);
}
+ mWifiSummaryText.setVisibility(View.VISIBLE);
mWifiSummaryText.setText(summary);
}
- Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) throws Throwable {
+ Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) {
if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
return null;
}
@@ -198,5 +185,20 @@
shared.set(drawable);
return shared.get();
}
+
+ void updateEndIcon(int connectedState, int security) {
+ Drawable drawable = null;
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ drawable = mContext.getDrawable(R.drawable.ic_settings_24dp);
+ } else if (security != WifiEntry.SECURITY_NONE && security != WifiEntry.SECURITY_OWE) {
+ drawable = mContext.getDrawable(R.drawable.ic_friction_lock_closed);
+ }
+ if (drawable == null) {
+ mWifiEndIcon.setVisibility(View.GONE);
+ return;
+ }
+ mWifiEndIcon.setVisibility(View.VISIBLE);
+ mWifiEndIcon.setImageDrawable(drawable);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 52bf2df..b1db8a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -423,7 +423,10 @@
}
void onClickConnectedWifi() {
- mInternetDialogController.launchWifiNetworkDetailsSetting();
+ if (mConnectedWifiEntry == null) {
+ return;
+ }
+ mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey());
}
void onClickSeeMoreButton() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 62fa3d4..70f52ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -248,8 +248,7 @@
return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
- protected Intent getWifiDetailsSettingsIntent() {
- String key = mConnectedEntry == null ? null : mConnectedEntry.getKey();
+ protected Intent getWifiDetailsSettingsIntent(String key) {
if (TextUtils.isEmpty(key)) {
if (DEBUG) {
Log.d(TAG, "connected entry's key is empty");
@@ -338,6 +337,9 @@
}
Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) {
+ if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+ return null;
+ }
final Drawable drawable =
mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), wifiEntry.getLevel());
if (drawable == null) {
@@ -558,8 +560,8 @@
mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
}
- void launchWifiNetworkDetailsSetting() {
- Intent intent = getWifiDetailsSettingsIntent();
+ void launchWifiNetworkDetailsSetting(String key) {
+ Intent intent = getWifiDetailsSettingsIntent(key);
if (intent != null) {
mCallback.dismissDialog();
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
@@ -739,7 +741,7 @@
final Intent intent = new Intent("com.android.settings.WIFI_DIALOG")
.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mActivityStarter.startActivity(intent, true);
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
} else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
Toast.makeText(mContext, R.string.wifi_failed_connect_message,
Toast.LENGTH_SHORT).show();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index b563d86..21d0338 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -311,7 +311,7 @@
setBounds(0, 0, newIconSize, newIconSize)
}
// Add the action icon to the Smart Action button.
- setCompoundDrawables(iconDrawable, null, null, null)
+ setCompoundDrawablesRelative(iconDrawable, null, null, null)
val onClickListener = View.OnClickListener {
onSmartActionClick(entry, smartActions, actionIndex, action)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 4e33529..a3e27be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -503,15 +503,15 @@
}
/**
- * Returns the combined width of the left drawable (the action icon) and the padding between the
- * drawable and the button text.
+ * Returns the combined width of the start drawable (the action icon) and the padding between
+ * the drawable and the button text.
*/
- private int getLeftCompoundDrawableWidthWithPadding(Button button) {
- Drawable[] drawables = button.getCompoundDrawables();
- Drawable leftDrawable = drawables[0];
- if (leftDrawable == null) return 0;
+ private int getStartCompoundDrawableWidthWithPadding(Button button) {
+ Drawable[] drawables = button.getCompoundDrawablesRelative();
+ Drawable startDrawable = drawables[0];
+ if (startDrawable == null) return 0;
- return leftDrawable.getBounds().width() + button.getCompoundDrawablePadding();
+ return startDrawable.getBounds().width() + button.getCompoundDrawablePadding();
}
private int squeezeButtonToTextWidth(Button button, int heightMeasureSpec, int textWidth) {
@@ -520,8 +520,8 @@
// Re-measure the squeezed smart reply button.
clearLayoutLineCount(button);
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
- button.getPaddingLeft() + button.getPaddingRight() + textWidth
- + getLeftCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
+ button.getPaddingStart() + button.getPaddingEnd() + textWidth
+ + getStartCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
button.measure(widthMeasureSpec, heightMeasureSpec);
final int newWidth = button.getMeasuredWidth();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index afbd2f2..4c7f959e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -17,6 +17,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -53,6 +54,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.RotationUtils;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.View;
@@ -243,31 +245,36 @@
false /* multipleRadius */, false /* fillCutout */);
// left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
+ final Point topRadius = new Point(testTopRadius, testTopRadius);
+ final Point bottomRadius = new Point(testBottomRadius, testBottomRadius);
View leftRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.left);
+ boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left);
+ verify(mScreenDecorations, atLeastOnce())
+ .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
+
View rightRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.right);
+ isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right);
verify(mScreenDecorations, atLeastOnce())
- .setSize(leftRoundedCorner, new Point(testTopRadius, testTopRadius));
- verify(mScreenDecorations, atLeastOnce())
- .setSize(rightRoundedCorner, new Point(testBottomRadius, testBottomRadius));
+ .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
+
leftRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.left);
+ isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left);
+ verify(mScreenDecorations, atLeastOnce())
+ .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
+
rightRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.right);
+ isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right);
verify(mScreenDecorations, atLeastOnce())
- .setSize(leftRoundedCorner, new Point(testTopRadius, testTopRadius));
- verify(mScreenDecorations, atLeastOnce())
- .setSize(rightRoundedCorner, new Point(testBottomRadius, testBottomRadius));
+ .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
}
@Test
@@ -308,13 +315,9 @@
true /* fillCutout */);
// top cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
// Top window is created for top cutout.
@@ -335,13 +338,9 @@
true /* fillCutout */);
// left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
// Left window is created for left cutout.
@@ -362,19 +361,15 @@
true /* fillCutout */);
// top cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- // Top window is created for rouned corner and top cutout.
+ // Top window is created for rounded corner and top cutout.
verify(mWindowManager, times(1))
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- // Bottom window is created for rouned corner.
+ // Bottom window is created for rounded corner.
verify(mWindowManager, times(1))
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
// Left window should be null.
@@ -390,19 +385,15 @@
true /* fillCutout */);
// left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- // Left window is created for rouned corner and left cutout.
+ // Left window is created for rounded corner and left cutout.
verify(mWindowManager, times(1))
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
- // Right window is created for rouned corner.
+ // Right window is created for rounded corner.
verify(mWindowManager, times(1))
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]), any());
// Top window should be null.
@@ -418,19 +409,15 @@
true /* fillCutout */);
// top and left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- // Top window is created for rouned corner and top cutout.
+ // Top window is created for rounded corner and top cutout.
verify(mWindowManager, times(1))
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- // Bottom window is created for rouned corner.
+ // Bottom window is created for rounded corner.
verify(mWindowManager, times(1))
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
// Left window is created for left cutout.
@@ -447,13 +434,9 @@
true /* fillCutout */);
// Set to short edge cutout(top).
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
verify(mWindowManager, times(1))
@@ -463,14 +446,9 @@
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
// Switch to long edge cutout(left).
- // left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), newBounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.onConfigurationChanged(new Configuration());
verify(mWindowManager, times(1))
@@ -487,13 +465,9 @@
false /* fillCutout */);
// top cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
assertNull(mScreenDecorations.mOverlays);
@@ -652,4 +626,19 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout);
}
+
+ private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) {
+ final int rotation = mContext.getDisplay().getRotation();
+ final Insets insets = RotationUtils.rotateInsets(safeInsets, rotation);
+ final Rect[] sorted = new Rect[BOUNDS_POSITION_LENGTH];
+ for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+ final int rotatedPos = ScreenDecorations.getBoundPositionFromRotation(i, rotation);
+ if (cutoutBounds[i] != null) {
+ RotationUtils.rotateBounds(cutoutBounds[i], new Rect(0, 0, 100, 200), rotation);
+ }
+ sorted[rotatedPos] = cutoutBounds[i];
+ }
+ return new DisplayCutout(insets, sorted[BOUNDS_POSITION_LEFT], sorted[BOUNDS_POSITION_TOP],
+ sorted[BOUNDS_POSITION_RIGHT], sorted[BOUNDS_POSITION_BOTTOM]);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
index fa5f70c..77946cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -10,21 +10,26 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.drawable.Drawable;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
import android.view.View;
import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.wifitrackerlib.WifiEntry;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.Arrays;
import java.util.List;
@@ -35,6 +40,11 @@
private static final String WIFI_TITLE = "Wi-Fi Title";
private static final String WIFI_SUMMARY = "Wi-Fi Summary";
+ private static final int GEAR_ICON_RES_ID = R.drawable.ic_settings_24dp;
+ private static final int LOCK_ICON_RES_ID = R.drawable.ic_friction_lock_closed;
+
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
@Mock
private WifiEntry mInternetWifiEntry;
@@ -46,13 +56,18 @@
private InternetDialogController mInternetDialogController;
@Mock
private WifiUtils.InternetIconInjector mWifiIconInjector;
+ @Mock
+ private Drawable mGearIcon;
+ @Mock
+ private Drawable mLockIcon;
+ private TestableResources mTestableResources;
private InternetAdapter mInternetAdapter;
private InternetAdapter.InternetViewHolder mViewHolder;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ mTestableResources = mContext.getOrCreateTestableResources();
when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
@@ -83,7 +98,7 @@
assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mWifiLockedIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -94,7 +109,7 @@
assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mWifiLockedIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
@@ -124,4 +139,29 @@
verify(mWifiIconInjector).getIcon(eq(true) /* noInternet */, anyInt());
}
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiConnected_updateGearIcon() {
+ mTestableResources.addOverride(GEAR_ICON_RES_ID, mGearIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_CONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mGearIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityPsk_updateLockIcon() {
+ mTestableResources.addOverride(LOCK_ICON_RES_ID, mLockIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mLockIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityNone_hideIcon() {
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_NONE);
+
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index cacc409..baddacc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -249,28 +249,17 @@
}
@Test
- public void getWifiDetailsSettingsIntent_withNoConnectedEntry_returnNull() {
- mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
-
- assertThat(mInternetDialogController.getWifiDetailsSettingsIntent()).isNull();
+ public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
}
@Test
- public void getWifiDetailsSettingsIntent_withNoConnectedEntryKey_returnNull() {
- when(mConnectedEntry.getKey()).thenReturn(null);
-
- assertThat(mInternetDialogController.getWifiDetailsSettingsIntent()).isNull();
+ public void getWifiDetailsSettingsIntent_withKey_returnIntent() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent("test_key")).isNotNull();
}
@Test
- public void getWifiDetailsSettingsIntent_withConnectedEntryKey_returnIntent() {
- when(mConnectedEntry.getKey()).thenReturn("test_key");
-
- assertThat(mInternetDialogController.getWifiDetailsSettingsIntent()).isNotNull();
- }
-
- @Test
- public void getWifiDrawable_withConnectedEntry_returnIntentIconWithCorrectColor() {
+ public void getInternetWifiDrawable_withConnectedEntry_returnIntentIconWithCorrectColor() {
final Drawable drawable = mock(Drawable.class);
when(mWifiIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(drawable);
@@ -281,20 +270,25 @@
}
@Test
- public void launchWifiNetworkDetailsSetting_withNoConnectedEntry_doNothing() {
- mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+ public void getInternetWifiDrawable_withWifiLevelUnreachable_returnNull() {
+ when(mConnectedEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
- mInternetDialogController.launchWifiNetworkDetailsSetting();
+ Drawable drawable = mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+ assertThat(drawable).isNull();
+ }
+
+ @Test
+ public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */);
verify(mActivityStarter, never())
.postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
}
@Test
- public void launchWifiNetworkDetailsSetting_withConnectedEntryKey_startActivity() {
- when(mConnectedEntry.getKey()).thenReturn("test_key");
-
- mInternetDialogController.launchWifiNetworkDetailsSetting();
+ public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key");
verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 32aee2b..ac04fa7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -581,8 +581,6 @@
// devices.
layout.setBaselineAligned(false);
- final boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-
// Add smart replies
Button previous = null;
SmartReplyView.SmartReplies smartReplies =
@@ -602,11 +600,7 @@
if (previous != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) previous.getLayoutParams();
- if (isRtl) {
- lp.leftMargin = mSpacing;
- } else {
- lp.rightMargin = mSpacing;
- }
+ lp.setMarginEnd(mSpacing);
}
layout.addView(current);
previous = current;
@@ -630,11 +624,7 @@
if (previous != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) previous.getLayoutParams();
- if (isRtl) {
- lp.leftMargin = mSpacing;
- } else {
- lp.rightMargin = mSpacing;
- }
+ lp.setMarginEnd(mSpacing);
}
layout.addView(current);
previous = current;
@@ -933,8 +923,8 @@
.collect(Collectors.toList());
Button singleLineButton = buttons.get(0);
Button doubleLineButton = buttons.get(1);
- Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
- Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
+ Drawable singleLineDrawable = singleLineButton.getCompoundDrawablesRelative()[0]; // start
+ Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawablesRelative()[0]; // start
assertEquals(singleLineDrawable.getBounds().width(),
doubleLineDrawable.getBounds().width());
assertEquals(singleLineDrawable.getBounds().height(),
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5c19ceb..ff480d1 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,6 +72,7 @@
import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -114,6 +115,8 @@
import com.android.server.wm.WindowProcessController;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -171,6 +174,27 @@
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
+ static final int CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY = 0;
+ static final int CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY = 1;
+ static final int CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME = 2;
+
+ @IntDef(prefix = { "CACHED_COMPAT_CHANGE_" }, value = {
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY,
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY,
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ static @interface CachedCompatChangeId{}
+
+ /**
+ * Mapping from CACHED_COMPAT_CHANGE_* to the actual compat change id.
+ */
+ static final long[] CACHED_COMPAT_CHANGE_IDS_MAPPING = new long[] {
+ PROCESS_CAPABILITY_CHANGE_ID,
+ CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ };
+
/**
* For some direct access we need to power manager.
*/
@@ -368,6 +392,12 @@
return mPlatformCompatCache;
}
+ boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, ApplicationInfo app,
+ boolean defaultValue) {
+ return getPlatformCompatCache().isChangeEnabled(
+ CACHED_COMPAT_CHANGE_IDS_MAPPING[cachedCompatChangeId], app, defaultValue);
+ }
+
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
this(service, processList, activeUids, createAdjusterThread());
}
@@ -1929,12 +1959,8 @@
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID, s.appInfo);
- } catch (RemoteException e) {
- }
+ final boolean enabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY);
if (enabled) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
@@ -2151,12 +2177,8 @@
// to client's state.
clientProcState = PROCESS_STATE_BOUND_TOP;
state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- PROCESS_CAPABILITY_CHANGE_ID, client.info);
- } catch (RemoteException e) {
- }
+ final boolean enabled = cstate.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
if (enabled) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
// TOP process passes all capabilities to the service.
@@ -2800,8 +2822,8 @@
state.setProcStateChanged(true);
}
} else if (state.hasReportedInteraction()) {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
: mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
@@ -2811,8 +2833,8 @@
maybeUpdateUsageStatsLSP(app, nowElapsed);
}
} else {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
: mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
@@ -2901,8 +2923,8 @@
if (mService.mUsageStatsService == null) {
return;
}
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
boolean isInteraction;
// To avoid some abuse patterns, we are going to be careful about what we consider
// to be an app interaction. Being the top activity doesn't count while the display
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index dc6bcd8..d4474d6 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -21,6 +21,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.OomAdjuster.CachedCompatChangeId;
import static com.android.server.am.ProcessRecord.TAG;
import android.annotation.ElapsedRealtimeLong;
@@ -377,6 +378,16 @@
@GuardedBy("mService")
private int mCachedIsReceivingBroadcast = VALUE_INVALID;
+ /**
+ * Cache the return value of PlatformCompat.isChangeEnabled().
+ */
+ @GuardedBy("mService")
+ private int[] mCachedCompatChanges = new int[] {
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME
+ };
+
@GuardedBy("mService")
private int mCachedAdj = ProcessList.INVALID_ADJ;
@GuardedBy("mService")
@@ -1009,6 +1020,16 @@
}
@GuardedBy("mService")
+ boolean getCachedCompatChange(@CachedCompatChangeId int cachedCompatChangeId) {
+ if (mCachedCompatChanges[cachedCompatChangeId] == VALUE_INVALID) {
+ mCachedCompatChanges[cachedCompatChangeId] = mService.mOomAdjuster
+ .isChangeEnabled(cachedCompatChangeId, mApp.info, false /* default */)
+ ? VALUE_TRUE : VALUE_FALSE;
+ }
+ return mCachedCompatChanges[cachedCompatChangeId] == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
int adj, boolean foregroundActivities, boolean hasVisibleActivities, int procState,
int schedGroup, int appUid, int logUid, int processCurTop) {
@@ -1088,6 +1109,9 @@
mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
mCurProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
PROCESS_STATE_NONEXISTENT;
+ for (int i = 0; i < mCachedCompatChanges.length; i++) {
+ mCachedCompatChanges[i] = VALUE_INVALID;
+ }
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 0a1c77b..9764a16 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -26,6 +26,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.List;
import java.util.NoSuchElementException;
/**
@@ -70,26 +72,32 @@
}
/** Holder for wrapping multiple handlers into a single Callback. */
- protected static class CompositeCallback implements Callback {
+ public static class CompositeCallback implements Callback {
@NonNull
- private final Callback[] mCallbacks;
+ private final List<Callback> mCallbacks;
public CompositeCallback(@NonNull Callback... callbacks) {
- mCallbacks = callbacks;
+ mCallbacks = new ArrayList<>();
+
+ for (Callback callback : callbacks) {
+ if (callback != null) {
+ mCallbacks.add(callback);
+ }
+ }
}
@Override
public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- for (int i = 0; i < mCallbacks.length; i++) {
- mCallbacks[i].onClientStarted(clientMonitor);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onClientStarted(clientMonitor);
}
}
@Override
public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- for (int i = mCallbacks.length - 1; i >= 0; i--) {
- mCallbacks[i].onClientFinished(clientMonitor, success);
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).onClientFinished(clientMonitor, success);
}
}
}
@@ -256,7 +264,7 @@
return mToken;
}
- public final int getSensorId() {
+ public int getSensorId() {
return mSensorId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a15e14b..9191b8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -31,7 +31,7 @@
/**
* A class to keep track of the enrollment state for a given client.
*/
-public abstract class EnrollClient<T> extends AcquisitionClient<T> {
+public abstract class EnrollClient<T> extends AcquisitionClient<T> implements EnrollmentModifier {
private static final String TAG = "Biometrics/EnrollClient";
@@ -40,6 +40,7 @@
protected final BiometricUtils mBiometricUtils;
private long mEnrollmentStartTimeMs;
+ private final boolean mHasEnrollmentsBeforeStarting;
/**
* @return true if the user has already enrolled the maximum number of templates.
@@ -56,6 +57,18 @@
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
+ mHasEnrollmentsBeforeStarting = hasEnrollments();
+ }
+
+ @Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = hasEnrollments();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
}
public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
new file mode 100644
index 0000000..c2f909b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+/**
+ * Interface for {@link BaseClientMonitor} subclasses that affect the state of enrollment.
+ */
+public interface EnrollmentModifier {
+
+ /**
+ * Callers should typically check this after
+ * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)}
+ *
+ * @return true if the user has gone from:
+ * 1) none-enrolled --> enrolled
+ * 2) enrolled --> none-enrolled
+ * but NOT any-enrolled --> more-enrolled
+ */
+ boolean hasEnrollmentStateChanged();
+
+ /**
+ * @return true if the user has any enrollments
+ */
+ boolean hasEnrollments();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 282261e..579dfd6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -40,7 +40,8 @@
* {@link #onRemoved(BiometricAuthenticator.Identifier, int)} returns true/
*/
public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer {
+ extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer,
+ EnrollmentModifier {
private static final String TAG = "Biometrics/InternalCleanupClient";
@@ -61,6 +62,7 @@
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
private final List<S> mEnrolledList;
+ private final boolean mHasEnrollmentsBeforeStarting;
private BaseClientMonitor mCurrentTask;
private final Callback mEnumerateCallback = new Callback() {
@@ -115,6 +117,7 @@
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mEnrolledList = enrolledList;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
private void startCleanupUnknownHalTemplates() {
@@ -166,6 +169,18 @@
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public void onEnumerationResult(BiometricAuthenticator.Identifier identifier,
int remaining) {
if (!(mCurrentTask instanceof InternalEnumerateClient)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 383efce..2a6677e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -33,12 +33,13 @@
* A class to keep track of the remove state for a given client.
*/
public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements RemovalConsumer {
+ extends HalClientMonitor<T> implements RemovalConsumer, EnrollmentModifier {
private static final String TAG = "Biometrics/RemovalClient";
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
+ private final boolean mHasEnrollmentsBeforeStarting;
public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -49,6 +50,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
@Override
@@ -91,6 +93,18 @@
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public int getProtoEnum() {
return BiometricsProto.CM_REMOVE;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index f0a8b9c..f35bb7f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -35,6 +35,7 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
@@ -62,11 +63,13 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Pair;
@@ -111,6 +114,7 @@
private final FingerprintServiceWrapper mServiceWrapper;
@NonNull private final List<ServiceProvider> mServiceProviders;
@NonNull private final FingerprintStateCallback mFingerprintStateCallback;
+ @NonNull private final Handler mHandler;
@GuardedBy("mLock")
@NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
@@ -125,6 +129,37 @@
*/
public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) {
mFingerprintStateCallback.registerFingerprintStateListener(listener);
+ broadcastCurrentEnrollmentState(listener);
+ }
+
+ /**
+ * @param listener if non-null, notifies only this listener. if null, notifies all listeners
+ * in {@link FingerprintStateCallback}. This is slightly ugly, but reduces
+ * redundant code.
+ */
+ private void broadcastCurrentEnrollmentState(@Nullable IFingerprintStateListener listener) {
+ final UserManager um = UserManager.get(getContext());
+ synchronized (mLock) {
+ // Update the new listener with current state of all sensors
+ for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
+ final ServiceProvider provider = getProviderForSensor(prop.sensorId);
+ for (UserInfo userInfo : um.getAliveUsers()) {
+ final boolean enrolled = !provider
+ .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
+
+ // Defer this work and allow the loop to release the lock sooner
+ mHandler.post(() -> {
+ if (listener != null) {
+ mFingerprintStateCallback.notifyFingerprintEnrollmentStateChanged(
+ listener, userInfo.id, prop.sensorId, enrolled);
+ } else {
+ mFingerprintStateCallback.notifyAllFingerprintEnrollmentStateChanged(
+ userInfo.id, prop.sensorId, enrolled);
+ }
+ });
+ }
+ }
+ }
}
/**
@@ -143,8 +178,7 @@
return null;
}
- return provider.createTestSession(sensorId, callback, mFingerprintStateCallback,
- opPackageName);
+ return provider.createTestSession(sensorId, callback, opPackageName);
}
@Override
@@ -227,7 +261,7 @@
}
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
- receiver, opPackageName, enrollReason, mFingerprintStateCallback);
+ receiver, opPackageName, enrollReason);
}
@Override // Binder call
@@ -306,7 +340,7 @@
}
return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
- restricted, statsClient, isKeyguard, mFingerprintStateCallback);
+ restricted, statsClient, isKeyguard);
}
private long authenticateWithPrompt(
@@ -414,7 +448,7 @@
return provider.second.scheduleFingerDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName,
- BiometricsProtoEnums.CLIENT_KEYGUARD, mFingerprintStateCallback);
+ BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@Override // Binder call
@@ -433,7 +467,7 @@
provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
- allowBackgroundAuthentication, mFingerprintStateCallback);
+ allowBackgroundAuthentication);
}
@Override // Binder call
@@ -687,27 +721,6 @@
.isEmpty();
}
- @Override // Binder call
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors,
- @NonNull String opPackageName) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-
- for (FingerprintSensorPropertiesInternal prop : sensors) {
- final ServiceProvider provider = getProviderForSensor(prop.sensorId);
- if (provider == null) {
- Slog.w(TAG, "Null provider for sensorId: " + prop.sensorId
- + ", caller: " + opPackageName);
- continue;
- }
-
- if (!provider.getEnrolledFingerprints(prop.sensorId, userId).isEmpty()) {
- return true;
- }
- }
- return false;
- }
-
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -797,10 +810,12 @@
&& Settings.Secure.getIntForUser(getContext().getContentResolver(),
Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
UserHandle.USER_CURRENT) != 0) {
- fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
- fingerprint21 = Fingerprint21.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
mServiceProviders.add(fingerprint21);
@@ -823,8 +838,9 @@
try {
final SensorProps[] props = fp.getSensorProps();
final FingerprintProvider provider =
- new FingerprintProvider(getContext(), props, instance,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
+ instance, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher);
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -878,6 +894,7 @@
}
}
+ broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
broadcastAllAuthenticatorsRegistered();
});
}
@@ -975,6 +992,7 @@
mFingerprintStateCallback = new FingerprintStateCallback();
mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
mSensorProps = new ArrayList<>();
+ mHandler = new Handler(Looper.getMainLooper());
}
// Notifies the callbacks that all of the authenticators have been registered and removes the
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index 5f998d8..0050a89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -23,6 +23,7 @@
import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintStateListener;
import android.os.RemoteException;
@@ -31,6 +32,9 @@
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.EnrollmentModifier;
+import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,9 +43,11 @@
* A callback for receiving notifications about changes in fingerprint state.
*/
public class FingerprintStateCallback implements BaseClientMonitor.Callback {
- private @FingerprintStateListener.State int mFingerprintState;
+
@NonNull private final CopyOnWriteArrayList<IFingerprintStateListener>
- mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+ mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+
+ private @FingerprintStateListener.State int mFingerprintState;
public FingerprintStateCallback() {
mFingerprintState = STATE_IDLE;
@@ -54,8 +60,9 @@
@Override
public void onClientStarted(@NonNull BaseClientMonitor client) {
final int previousFingerprintState = mFingerprintState;
+
if (client instanceof AuthenticationClient) {
- AuthenticationClient authClient = (AuthenticationClient) client;
+ final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
if (authClient.isKeyguard()) {
mFingerprintState = STATE_KEYGUARD_AUTH;
} else if (authClient.isBiometricPrompt()) {
@@ -70,6 +77,7 @@
"Other authentication client: " + Utils.getClientName(client));
mFingerprintState = STATE_IDLE;
}
+
Slog.d(FingerprintService.TAG, "Fps state updated from " + previousFingerprintState
+ " to " + mFingerprintState + ", client " + client);
notifyFingerprintStateListeners(mFingerprintState);
@@ -81,6 +89,18 @@
Slog.d(FingerprintService.TAG,
"Client finished, fps state updated to " + mFingerprintState + ", client "
+ client);
+
+ if (client instanceof EnrollmentModifier) {
+ EnrollmentModifier enrollmentModifier = (EnrollmentModifier) client;
+ final boolean enrollmentStateChanged = enrollmentModifier.hasEnrollmentStateChanged();
+ Slog.d(FingerprintService.TAG, "Enrollment state changed: " + enrollmentStateChanged);
+ if (enrollmentStateChanged) {
+ notifyAllFingerprintEnrollmentStateChanged(client.getTargetUserId(),
+ client.getSensorId(),
+ enrollmentModifier.hasEnrollments());
+ }
+ }
+
notifyFingerprintStateListeners(mFingerprintState);
}
@@ -95,6 +115,32 @@
}
/**
+ * This should be invoked when:
+ * 1) Enrolled --> None-enrolled
+ * 2) None-enrolled --> enrolled
+ * 3) HAL becomes ready
+ * 4) Listener is registered
+ */
+ void notifyAllFingerprintEnrollmentStateChanged(int userId, int sensorId,
+ boolean hasEnrollments) {
+ for (IFingerprintStateListener listener : mFingerprintStateListeners) {
+ notifyFingerprintEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
+ }
+ }
+
+ /**
+ * Notifies the listener of enrollment state changes.
+ */
+ void notifyFingerprintEnrollmentStateChanged(@NonNull IFingerprintStateListener listener,
+ int userId, int sensorId, boolean hasEnrollments) {
+ try {
+ listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
+ } catch (RemoteException e) {
+ Slog.e(FingerprintService.TAG, "Remote exception", e);
+ }
+ }
+
+ /**
* Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward
* updates in fingerprint sensor state to the SideFpNsEventHandler
* @param listener
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index b9fcd8e..1772f81 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -90,27 +90,23 @@
*/
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFingerprintServiceReceiver receiver,
- @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
void cancelEnrollment(int sensorId, @NonNull IBinder token);
long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ int statsClient);
void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ boolean allowBackgroundAuthentication);
long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ boolean allowBackgroundAuthentication);
void startPreparedClient(int sensorId, int cookie);
@@ -169,6 +165,5 @@
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 29f2f20..2b50b96 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 377feca..ca83dda 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -82,6 +82,7 @@
private boolean mTestHalEnabled;
@NonNull private final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -130,10 +131,13 @@
}
}
- public FingerprintProvider(@NonNull Context context, @NonNull SensorProps[] props,
- @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ public FingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
+ @NonNull SensorProps[] props, @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
mHandler = new Handler(Looper.getMainLooper());
@@ -335,8 +339,7 @@
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
.maxEnrollmentsPerUser;
@@ -350,13 +353,13 @@
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
scheduleInvalidationRequest(sensorId, userId);
@@ -374,17 +377,15 @@
@Override
public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
final long id = mRequestCounter.incrementAndGet();
-
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
return id;
@@ -394,8 +395,7 @@
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
@@ -405,7 +405,7 @@
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@@ -413,13 +413,11 @@
public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ boolean allowBackgroundAuthentication) {
final long id = mRequestCounter.incrementAndGet();
scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
- opPackageName, id, restricted, statsClient, allowBackgroundAuthentication,
- fingerprintStateCallback);
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
return id;
}
@@ -466,7 +464,7 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@@ -481,7 +479,8 @@
mContext.getOpPackageName(), sensorId, enrolledList,
FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client, callback);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
});
}
@@ -626,9 +625,8 @@
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
- return mSensors.get(sensorId).createTestSession(callback, fingerprintStateCallback);
+ return mSensors.get(sensorId).createTestSession(callback, mFingerprintStateCallback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index c00daff..79c6b1b3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index f17bcc8..d2882aa4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -102,6 +102,7 @@
private boolean mTestHalEnabled;
final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
private final ActivityTaskManager mActivityTaskManager;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
private final BiometricScheduler mScheduler;
@@ -317,11 +318,13 @@
}
Fingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mSensorProperties = sensorProps;
mSensorId = sensorProps.sensorId;
@@ -351,6 +354,7 @@
}
public static Fingerprint21 newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -362,8 +366,8 @@
final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler,
scheduler);
- return new Fingerprint21(context, sensorProps, scheduler, handler, lockoutResetDispatcher,
- controller);
+ return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
@@ -557,8 +561,7 @@
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -570,13 +573,13 @@
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
// Update authenticatorIds
scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
@@ -597,10 +600,8 @@
@Override
public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
final long id = mRequestCounter.incrementAndGet();
-
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -609,7 +610,7 @@
mLazyDaemon, token, id, listener, userId, opPackageName,
mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
return id;
@@ -619,8 +620,7 @@
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
@NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -631,7 +631,7 @@
mSensorProperties.sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
allowBackgroundAuthentication, mSensorProperties);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -639,13 +639,11 @@
public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
@NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ boolean allowBackgroundAuthentication) {
final long id = mRequestCounter.incrementAndGet();
scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
- opPackageName, id, restricted, statsClient, allowBackgroundAuthentication,
- fingerprintStateCallback);
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
return id;
}
@@ -672,7 +670,7 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -689,7 +687,7 @@
0 /* fingerprintId */, userId, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -711,7 +709,8 @@
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
- scheduleInternalCleanup(userId, callback);
+ scheduleInternalCleanup(userId, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
}
@Override
@@ -919,9 +918,8 @@
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
- fingerprintStateCallback, this, mHalResultController);
+ mFingerprintStateCallback, this, mHalResultController);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 24ce867..79ad8e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,6 +26,7 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -42,6 +43,7 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.util.ArrayList;
@@ -270,6 +272,7 @@
}
public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -280,8 +283,8 @@
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
- return new Fingerprint21UdfpsMock(context, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
+ handler, lockoutResetDispatcher, controller);
}
private static abstract class FakeFingerRunnable implements Runnable {
@@ -400,17 +403,19 @@
// internal preemption logic is not run.
mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
operationId, user, cookie, listener, opPackageName, restricted, statsClient,
- isKeyguard, null /* fingerprintStateCallback */);
+ isKeyguard);
}
}
private Fingerprint21UdfpsMock(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull MockHalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 383b392..806a5dd 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,7 +19,6 @@
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
@@ -36,7 +35,6 @@
import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -48,8 +46,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.DisplayThread;
import com.android.server.LocalServices;
-import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.policy.DeviceStatePolicyImpl;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -93,10 +91,9 @@
private static final boolean DEBUG = false;
private final Object mLock = new Object();
- // Internal system service thread used to dispatch calls to the policy and to registered
+ // Handler on the {@link DisplayThread} used to dispatch calls to the policy and to registered
// callbacks though its handler (mHandler). Provides a guarantee of callback order when
// leveraging mHandler and also enables posting messages with the service lock held.
- private final HandlerThread mHandlerThread;
private final Handler mHandler;
@NonNull
private final DeviceStatePolicy mDeviceStatePolicy;
@@ -149,11 +146,10 @@
@VisibleForTesting
DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
super(context);
- // Service thread assigned THREAD_PRIORITY_DISPLAY because this service indirectly drives
+ // We use the DisplayThread because this service indirectly drives
// display (on/off) and window (position) events through its callbacks.
- mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_DISPLAY, false /* allowIo */);
- mHandlerThread.start();
- mHandler = mHandlerThread.getThreadHandler();
+ DisplayThread displayThread = DisplayThread.get();
+ mHandler = new Handler(displayThread.getLooper());
mOverrideRequestController = new OverrideRequestController(
this::onOverrideRequestStatusChangedLocked);
mDeviceStatePolicy = policy;
@@ -552,7 +548,7 @@
}
ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied,
- mHandlerThread.getThreadHandler());
+ mHandler);
try {
callback.asBinder().linkToDeath(record, 0);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 34d2b01..a592192 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -303,6 +303,9 @@
@Override
public Point getDisplaySurfaceDefaultSize() {
+ if (mSurface == null) {
+ return null;
+ }
return mSurface.getDefaultSize();
}
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 7112ae1..628a322 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -44,6 +45,8 @@
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Slog;
@@ -392,19 +395,28 @@
+ "--context <snooze-criterion-id>) <key>");
return 1;
}
- if (null == mDirectService.getNotificationRecord(key)) {
- pw.println("error: no notification matching key: " + key);
- return 1;
- }
if (duration > 0 || criterion != null) {
+ ShellNls nls = new ShellNls();
+ nls.registerAsSystemService(mDirectService.getContext(),
+ new ComponentName(nls.getClass().getPackageName(),
+ nls.getClass().getName()),
+ ActivityManager.getCurrentUser());
+ if (!waitForBind(nls)) {
+ pw.println("error: could not bind a listener in time");
+ return 1;
+ }
if (duration > 0) {
pw.println(String.format("snoozing <%s> until time: %s", key,
new Date(System.currentTimeMillis() + duration)));
+ nls.snoozeNotification(key, duration);
} else {
pw.println(String.format("snoozing <%s> until criterion: %s", key,
criterion));
+ nls.snoozeNotification(key, criterion);
}
- mDirectService.snoozeNotificationInt(key, duration, criterion, null);
+ waitForSnooze(nls, key);
+ nls.unregisterAsSystemService();
+ waitForUnbind(nls);
} else {
pw.println("error: invalid value for --" + subflag + ": " + flagarg);
return 1;
@@ -527,14 +539,17 @@
final PendingIntent pi;
if ("broadcast".equals(intentKind)) {
pi = PendingIntent.getBroadcastAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED,
UserHandle.CURRENT);
} else if ("service".equals(intentKind)) {
pi = PendingIntent.getService(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED);
} else {
pi = PendingIntent.getActivityAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
UserHandle.CURRENT);
}
builder.setContentIntent(pi);
@@ -685,9 +700,79 @@
return 0;
}
+ private void waitForSnooze(ShellNls nls, String key) {
+ for (int i = 0; i < 20; i++) {
+ StatusBarNotification[] sbns = nls.getSnoozedNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getKey().equals(key)) {
+ return;
+ }
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return;
+ }
+
+ private boolean waitForBind(ShellNls nls) {
+ for (int i = 0; i < 20; i++) {
+ if (nls.isConnected) {
+ Slog.i(TAG, "Bound Shell NLS");
+ return true;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return false;
+ }
+
+ private void waitForUnbind(ShellNls nls) {
+ for (int i = 0; i < 10; i++) {
+ if (!nls.isConnected) {
+ Slog.i(TAG, "Unbound Shell NLS");
+ return;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
@Override
public void onHelp() {
getOutPrintWriter().println(USAGE);
}
+
+ @SuppressLint("OverrideAbstract")
+ private static class ShellNls extends NotificationListenerService {
+ private static ShellNls
+ sNotificationListenerInstance = null;
+ boolean isConnected;
+
+ @Override
+ public void onListenerConnected() {
+ super.onListenerConnected();
+ sNotificationListenerInstance = this;
+ isConnected = true;
+ }
+ @Override
+ public void onListenerDisconnected() {
+ isConnected = false;
+ }
+
+ public static ShellNls getInstance() {
+ return sNotificationListenerInstance;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 73faca7..8f777e6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4897,7 +4897,12 @@
// dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report
// the stale visible state, because the state will be updated after the app transition.
// So tries to report the actual visible state again where the state is changed.
- if (task != null) task.dispatchTaskInfoChangedIfNeeded(false /* force */);
+ if (!mTaskSupervisor.inActivityVisibilityUpdate()) {
+ final Task task = getOrganizedTask();
+ if (task != null) {
+ task.dispatchTaskInfoChangedIfNeeded(false /* force */);
+ }
+ }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
isVisible(), mVisibleRequested);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7438fa3..199159e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -61,7 +61,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
@@ -88,6 +87,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
@@ -305,7 +305,8 @@
@VisibleForTesting IBinder mTokenToMirror = null;
/**
- * The surface for mirroring the contents of this hierarchy.
+ * The surface for mirroring the contents of this hierarchy, or null if layer mirroring is
+ * temporarily disabled.
*/
private SurfaceControl mMirroredSurface = null;
@@ -314,6 +315,11 @@
*/
private Rect mLastMirroredDisplayAreaBounds = null;
+ /**
+ * The last state of the display.
+ */
+ private int mLastDisplayState;
+
// Contains all IME window containers. Note that the z-ordering of the IME windows will depend
// on the IME target. We mainly have this container grouping so we can keep track of all the IME
// window containers together and move them in-sync if/when needed. We use a subclass of
@@ -1142,10 +1148,6 @@
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
-
- // Check if this DisplayContent is for a new VirtualDisplay, that should use layer mirroring
- // to capture the contents of a DisplayArea.
- startMirrorIfNeeded();
}
boolean isReady() {
@@ -2504,19 +2506,42 @@
// Update mirroring surface for MediaProjection, if this DisplayContent is being used
// for layer mirroring.
- if (mMirroredSurface != null) {
- // Retrieve the size of the DisplayArea to mirror, and continue with the update if the
- // bounds have changed.
+ if (isCurrentlyMirroring() && mLastMirroredDisplayAreaBounds != null) {
+ // Mirroring has already begun, but update mirroring since the display is now on.
final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer(
mTokenToMirror);
- if (wc != null && mLastMirroredDisplayAreaBounds != null) {
- // Retrieve the size of the DisplayArea to mirror, and continue with the update
- // if the bounds or orientation has changed.
- final Rect displayAreaBounds = wc.getDisplayContent().getBounds();
- int displayAreaOrientation = wc.getDisplayContent().getOrientation();
- if (!mLastMirroredDisplayAreaBounds.equals(displayAreaBounds)
- || lastOrientation != displayAreaOrientation) {
- updateMirroredSurface(mWmService.mTransactionFactory.get(), displayAreaBounds);
+ if (wc == null) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to retrieve window container to update layer mirroring for "
+ + "display %d",
+ mDisplayId);
+ return;
+ }
+
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d was already layer mirroring, so apply transformations if necessary",
+ mDisplayId);
+ // Retrieve the size of the DisplayArea to mirror, and continue with the update
+ // if the bounds or orientation has changed.
+ final Rect displayAreaBounds = wc.getDisplayContent().getBounds();
+ int displayAreaOrientation = wc.getDisplayContent().getOrientation();
+ if (!mLastMirroredDisplayAreaBounds.equals(displayAreaBounds)
+ || lastOrientation != displayAreaOrientation) {
+ Point surfaceSize = fetchSurfaceSizeIfPresent();
+ if (surfaceSize != null) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Going ahead with updating layer mirroring for display %d to new "
+ + "bounds %s and/or orientation %d.",
+ mDisplayId, displayAreaBounds, displayAreaOrientation);
+ updateMirroredSurface(mWmService.mTransactionFactory.get(),
+ displayAreaBounds, surfaceSize);
+ } else {
+ // If the surface removed, do nothing. We will handle this via onDisplayChanged
+ // (the display will be off if the surface is removed).
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to update layer mirroring for display %d to new bounds %s"
+ + " and/or orientation %d, since the surface is not available.",
+ mDisplayId, displayAreaBounds, displayAreaOrientation);
}
}
}
@@ -4405,6 +4430,7 @@
mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
true /* inTraversal, must call performTraversalInTrans... below */);
}
+ // If the display now has content, or no longer has content, update layer mirroring.
updateMirroring();
final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
@@ -5504,6 +5530,15 @@
} else if (displayState == Display.STATE_ON) {
mOffTokenAcquirer.release(mDisplayId);
}
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d state is now (%d), so update layer mirroring?",
+ mDisplayId, displayState);
+ if (mLastDisplayState != displayState) {
+ // If state is on due to surface being added, then start layer mirroring.
+ // If state is off due to surface being removed, then stop layer mirroring.
+ updateMirroring();
+ }
+ mLastDisplayState = displayState;
}
mWmService.requestTraversal();
}
@@ -5983,31 +6018,48 @@
* back to original MediaProjection approach.
*/
private void startMirrorIfNeeded() {
- // Only mirror if this display does not have its own content.
- if (mLastHasContent) {
+ // Only mirror if this display does not have its own content, is not mirroring already,
+ // and if this display is on (it has a surface to write output to).
+ if (mLastHasContent || isCurrentlyMirroring() || mDisplay.getState() == Display.STATE_OFF) {
return;
}
+
// Given the WindowToken of the DisplayArea to mirror, retrieve the associated
// SurfaceControl.
IBinder tokenToMirror = mWmService.mDisplayManagerInternal.getWindowTokenClientToMirror(
mDisplayId);
-
if (tokenToMirror == null) {
// This DisplayContent instance is not involved in layer mirroring. If the display
// has been created for capturing, fall back to prior MediaProjection approach.
return;
}
+
final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer(
tokenToMirror);
if (wc == null) {
// Un-set the window token to mirror for this VirtualDisplay, to fall back to the
// original MediaProjection approach.
mWmService.mDisplayManagerInternal.setWindowTokenClientToMirror(mDisplayId, null);
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to retrieve window container to start layer mirroring for display %d",
+ mDisplayId);
return;
}
- SurfaceControl sc = wc.getDisplayContent().getSurfaceControl();
+
+ Point surfaceSize = fetchSurfaceSizeIfPresent();
+ if (surfaceSize == null) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to start layer mirroring for display %d since the surface is not "
+ + "available.",
+ mDisplayId);
+ return;
+ }
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d has no content and is on, so start layer mirroring for state %d",
+ mDisplayId, mDisplay.getState());
// Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
+ SurfaceControl sc = wc.getDisplayContent().getSurfaceControl();
mMirroredSurface = SurfaceControl.mirrorSurface(sc);
SurfaceControl.Transaction transaction = mWmService.mTransactionFactory.get()
// Set the mMirroredSurface's parent to the root SurfaceControl for this
@@ -6020,7 +6072,7 @@
// VirtualDisplay will show up as part of the mirrored content.
.reparent(mWindowingLayer, null);
// Retrieve the size of the DisplayArea to mirror.
- updateMirroredSurface(transaction, wc.getDisplayContent().getBounds());
+ updateMirroredSurface(transaction, wc.getDisplayContent().getBounds(), surfaceSize);
mTokenToMirror = tokenToMirror;
// No need to clean up. In SurfaceFlinger, parents hold references to their children. The
@@ -6030,11 +6082,18 @@
}
/**
- * Start or stop mirroring if this DisplayContent now has content, or no longer has content.
+ * Start mirroring if this DisplayContent no longer has content. Stop mirroring if it now
+ * has content or the display is not on.
*/
private void updateMirroring() {
- if (mLastHasContent && mMirroredSurface != null) {
- // Display now has content, so stop mirroring to it.
+ if (isCurrentlyMirroring() && (mLastHasContent
+ || mDisplay.getState() == Display.STATE_OFF)) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d has content (%b) so disable layer mirroring", mDisplayId,
+ mLastHasContent);
+ // If the display is not on and it is a virtual display, then it no longer has an
+ // associated surface to write output to.
+ // If the display now has content, stop mirroring to it.
mWmService.mTransactionFactory.get()
// Remove the reference to mMirroredSurface, to clean up associated memory.
.remove(mMirroredSurface)
@@ -6045,8 +6104,9 @@
// Stop mirroring by destroying the reference to the mirrored layer.
mMirroredSurface = null;
// Do not un-set the token, in case content is removed and mirroring should begin again.
- } else if (!mLastHasContent && mMirroredSurface == null) {
- // Display no longer has content, so start mirroring to it.
+ } else {
+ // Display no longer has content, or now has a surface to write to, so try to start
+ // mirroring to it.
startMirrorIfNeeded();
}
}
@@ -6055,21 +6115,15 @@
* Apply transformations to the mirrored surface to ensure the captured contents are scaled to
* fit and centred in the output surface.
*
- * @param transaction the transaction to include transformations of mMirroredSurface
- * to. Transaction is not applied before returning.
- * @param displayAreaBounds bounds of the DisplayArea to mirror to the surface provided by
- * the app.
+ * @param transaction the transaction to include transformations of mMirroredSurface
+ * to. Transaction is not applied before returning.
+ * @param displayAreaBounds bounds of the DisplayArea to mirror to the surface provided by
+ * the app.
+ * @param surfaceSize the default size of the surface to write the display area content to
*/
@VisibleForTesting
void updateMirroredSurface(SurfaceControl.Transaction transaction,
- Rect displayAreaBounds) {
- // Retrieve the default size of the surface the app provided to
- // MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface,
- // since it reads out buffers from the surface, and SurfaceFlinger is the producer since
- // it writes the mirrored layers to the buffers.
- final Point surfaceSize = mWmService.mDisplayManagerInternal.getDisplaySurfaceDefaultSize(
- mDisplayId);
-
+ Rect displayAreaBounds, Point surfaceSize) {
// Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
// output surface.
float scaleX = surfaceSize.x / (float) displayAreaBounds.width();
@@ -6104,6 +6158,36 @@
mLastMirroredDisplayAreaBounds = new Rect(displayAreaBounds);
}
+ /**
+ * Returns a non-null {@link Point} if the surface is present, or null otherwise
+ */
+ Point fetchSurfaceSizeIfPresent() {
+ // Retrieve the default size of the surface the app provided to
+ // MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface,
+ // since it reads out buffers from the surface, and SurfaceFlinger is the producer since
+ // it writes the mirrored layers to the buffers.
+ Point surfaceSize = mWmService.mDisplayManagerInternal.getDisplaySurfaceDefaultSize(
+ mDisplayId);
+ if (surfaceSize == null) {
+ // Layer mirroring started with a null surface, so do not apply any transformations yet.
+ // State of virtual display will change to 'ON' when the surface is set.
+ // will get event DISPLAY_DEVICE_EVENT_CHANGED
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Provided surface for layer mirroring on display %d is not present, so do not"
+ + " update the surface",
+ mDisplayId);
+ return null;
+ }
+ return surfaceSize;
+ }
+
+ /**
+ * Returns {@code true} if this DisplayContent is currently layer mirroring.
+ */
+ boolean isCurrentlyMirroring() {
+ return mTokenToMirror != null && mMirroredSurface != null;
+ }
+
/** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
new file mode 100644
index 0000000..09b5c5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class CompositeCallbackTest {
+
+ @Test
+ public void testNullCallback() {
+ BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback3 = null;
+
+ BaseClientMonitor.CompositeCallback callback = new BaseClientMonitor.CompositeCallback(
+ callback1, callback2, callback3);
+
+ BaseClientMonitor clientMonitor = mock(BaseClientMonitor.class);
+
+ callback.onClientStarted(clientMonitor);
+ verify(callback1).onClientStarted(eq(clientMonitor));
+ verify(callback2).onClientStarted(eq(clientMonitor));
+
+ callback.onClientFinished(clientMonitor, true /* success */);
+ verify(callback1).onClientFinished(eq(clientMonitor), eq(true));
+ verify(callback2).onClientFinished(eq(clientMonitor), eq(true));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
new file mode 100644
index 0000000..38e8dfa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.EnrollClient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class FingerprintStateCallbackTest {
+
+ private FingerprintStateCallback mCallback;
+
+ @Mock
+ FingerprintStateListener mFingerprintStateListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mCallback = new FingerprintStateCallback();
+ mCallback.registerFingerprintStateListener(mFingerprintStateListener);
+ }
+
+ @Test
+ public void testNoEnrollmentsToEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
+ true /* expectCallback */, true /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToNoEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
+ true /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToEnrollments_callbackNotNotified() {
+ testEnrollmentCallback(false /* changed */, true /* isNowEnrolled */,
+ false /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ private void testEnrollmentCallback(boolean changed, boolean isNowEnrolled,
+ boolean expectCallback, boolean expectedCallbackValue) {
+ EnrollClient<?> client = mock(EnrollClient.class);
+
+ final int userId = 10;
+ final int sensorId = 100;
+
+ when(client.hasEnrollmentStateChanged()).thenReturn(changed);
+ when(client.hasEnrollments()).thenReturn(isNowEnrolled);
+ when(client.getTargetUserId()).thenReturn(userId);
+ when(client.getSensorId()).thenReturn(sensorId);
+
+ mCallback.onClientFinished(client, true /* success */);
+ if (expectCallback) {
+ verify(mFingerprintStateListener).onEnrollmentsChanged(eq(userId), eq(sensorId),
+ eq(expectedCallbackValue));
+ } else {
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+ }
+
+ @Test
+ public void testAuthentication_enrollmentCallbackNeverNotified() {
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ mCallback.onClientFinished(client, true /* success */);
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 35c37ef..b51918e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -37,6 +37,7 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
@@ -58,6 +59,8 @@
private UserManager mUserManager;
@Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -87,8 +90,8 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mFingerprintProvider = new TestableFingerprintProvider(mContext, mFingerprintStateCallback,
+ mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@SuppressWarnings("rawtypes")
@@ -133,11 +136,12 @@
private static class TestableFingerprintProvider extends FingerprintProvider {
public TestableFingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- super(context, props, halInstanceName, lockoutResetDispatcher,
+ super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
gestureAvailabilityDispatcher);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index 0a0dcc9..f6b9209 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -41,6 +41,7 @@
import com.android.internal.R;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +68,8 @@
Fingerprint21.HalResultController mHalResultController;
@Mock
private BiometricScheduler mScheduler;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private LockoutResetDispatcher mLockoutResetDispatcher;
private Fingerprint21 mFingerprint21;
@@ -96,8 +99,9 @@
componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
resetLockoutRequiresHardwareAuthToken);
- mFingerprint21 = new TestableFingerprint21(mContext, sensorProps, mScheduler,
- new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController);
+ mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
+ mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
+ mHalResultController);
}
@Test
@@ -118,11 +122,13 @@
private static class TestableFingerprint21 extends Fingerprint21 {
TestableFingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 14dc33e..4ee0c60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -25,6 +25,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -111,6 +112,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.hardware.display.VirtualDisplay;
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.IBinder;
@@ -2292,7 +2294,7 @@
float yScale = 2f;
Rect displayAreaBounds = new Rect(0, 0, Math.round(surfaceSize.x * xScale),
Math.round(surfaceSize.y * yScale));
- virtualDisplay.updateMirroredSurface(mTransaction, displayAreaBounds);
+ virtualDisplay.updateMirroredSurface(mTransaction, displayAreaBounds, surfaceSize);
// THEN content in the captured DisplayArea is scaled to fit the surface size.
verify(mTransaction, atLeastOnce()).setMatrix(mirroredSurface, 1.0f / yScale, 0, 0,
@@ -2305,6 +2307,72 @@
mockSession.finishMocking();
}
+ @Test
+ public void testVirtualDisplayContent_withoutSurface() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ Point surfaceSize = new Point(
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ surfaceControlMirrors(surfaceSize);
+
+ // GIVEN a new VirtualDisplay with an associated surface.
+ final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */);
+ final int displayId = display.getDisplay().getDisplayId();
+ mWm.mRoot.onDisplayAdded(displayId);
+
+ // WHEN getting the DisplayContent for the new virtual display.
+ DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
+
+ // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
+ assertThat(actualDC.mTokenToMirror).isNull();
+
+ display.release();
+ mockSession.finishMocking();
+ }
+
+ @Test
+ public void testVirtualDisplayContent_withSurface() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ Point surfaceSize = new Point(
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ surfaceControlMirrors(surfaceSize);
+
+ // GIVEN a new VirtualDisplay with an associated surface.
+ final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
+ final int displayId = display.getDisplay().getDisplayId();
+ mWm.mRoot.onDisplayAdded(displayId);
+
+ // WHEN getting the DisplayContent for the new virtual display.
+ DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
+
+ // THEN mirroring is initiated for the default display's DisplayArea.
+ assertThat(actualDC.mTokenToMirror).isEqualTo(tokenToMirror);
+
+ display.release();
+ mockSession.finishMocking();
+ }
+
private class TestToken extends Binder {
}
@@ -2342,6 +2410,11 @@
return mirroredSurface;
}
+ private VirtualDisplay createVirtualDisplay(Point size, Surface surface) {
+ return mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", size.x, size.y,
+ DisplayMetrics.DENSITY_140, surface, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
+ }
+
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 7b6ccd3..49ea9df 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -689,8 +689,10 @@
try {
ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
if (aInfo.uid != uid) {
- throw new IllegalArgumentException("package " + packageName
+ Slog.w(TAG, "package " + packageName
+ " does not match caller's uid " + uid);
+ throw new IllegalArgumentException("package " + packageName
+ + " not found");
}
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("package " + packageName + " not found");
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a86e59..68ef754 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5161,6 +5161,16 @@
"display_no_data_notification_on_permanent_failure_bool";
/**
+ * Boolean indicating if the VoNR setting is visible in the Call Settings menu.
+ * If true, the VoNR setting menu will be visible. If false, the menu will be gone.
+ *
+ * Disabled by default.
+ *
+ * @hide
+ */
+ public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
+
+ /**
* Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
*
* @hide
@@ -5774,6 +5784,7 @@
sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, "");
sDefaults.putBoolean(KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL, false);
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
+ sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, false);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8f3172a..abab426 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12144,6 +12144,100 @@
}
/**
+ * No error. Operation succeeded.
+ * @hide
+ */
+ public static final int ENABLE_VONR_SUCCESS = 0;
+
+ /**
+ * Radio is not available.
+ * @hide
+ */
+ public static final int ENABLE_VONR_RADIO_NOT_AVAILABLE = 2;
+
+ /**
+ * Internal Radio error.
+ * @hide
+ */
+ public static final int ENABLE_VONR_RADIO_ERROR = 3;
+
+ /**
+ * Voice over NR enable/disable request is received when system is in invalid state.
+ * @hide
+ */
+ public static final int ENABLE_VONR_RADIO_INVALID_STATE = 4;
+
+ /**
+ * Voice over NR enable/disable request is not supported.
+ * @hide
+ */
+ public static final int ENABLE_VONR_REQUEST_NOT_SUPPORTED = 5;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"EnableVoNrResult"}, value = {
+ ENABLE_VONR_SUCCESS,
+ ENABLE_VONR_RADIO_NOT_AVAILABLE,
+ ENABLE_VONR_RADIO_ERROR,
+ ENABLE_VONR_RADIO_INVALID_STATE,
+ ENABLE_VONR_REQUEST_NOT_SUPPORTED})
+ public @interface EnableVoNrResult {}
+
+ /**
+ * Enable or disable Voice over NR (VoNR)
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
+ * @param enabled enable or disable VoNR.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public @EnableVoNrResult int setVoNrEnabled(boolean enabled) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.setVoNrEnabled(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#setVoNrEnabled", e);
+ }
+
+ return ENABLE_VONR_RADIO_INVALID_STATE;
+ }
+
+ /**
+ * Is Voice over NR (VoNR) enabled.
+ * @return true if Voice over NR (VoNR) is enabled else false. Enabled state does not mean
+ * voice call over NR is active or voice ove NR is available. It means the device is allowed to
+ * register IMS over NR.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isVoNrEnabled() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isVoNrEnabled(getSubId());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "isVoNrEnabled RemoteException", ex);
+ ex.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
* Carrier action to start or stop reporting default network available events.
*
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 018dabf..89e3925 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2228,6 +2228,20 @@
List<String> getEquivalentHomePlmns(int subId, String callingPackage, String callingFeatureId);
/**
+ * Enable or disable Voice over NR (VoNR)
+ * @param subId the subscription ID that this action applies to.
+ * @param enabled enable or disable VoNR.
+ * @return operation result.
+ */
+ int setVoNrEnabled(int subId, boolean enabled);
+
+ /**
+ * Is voice over NR enabled
+ * @return true if VoNR is enabled else false
+ */
+ boolean isVoNrEnabled(int subId);
+
+ /**
* Enable/Disable E-UTRA-NR Dual Connectivity
* @return operation result. See TelephonyManager.EnableNrDualConnectivityResult for
* details
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index fe8e671..fadc23b 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -528,6 +528,8 @@
int RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP = 222;
int RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP = 223;
int RIL_REQUEST_GET_SLICING_CONFIG = 224;
+ int RIL_REQUEST_ENABLE_VONR = 225;
+ int RIL_REQUEST_IS_VONR_ENABLED = 225;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;