Merge changes Iaf91e560,Ib40dcf0a into udc-qpr-dev
* changes:
[2/2] Migrating ShadeCarrierGroup mobile icons to modern status bar data pipeline
[1/2] Update new status bar data pipeline to support names for multiple sims
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 4851279..d0d76a4 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -454,12 +454,11 @@
*/
protected void onStart() {
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
- if (mContext != null
+ if (allowsRegisterDefaultOnBackInvokedCallback() && mContext != null
&& WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
// Add onBackPressed as default back behavior.
mDefaultBackCallback = this::onBackPressed;
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
- mDefaultBackCallback = null;
}
}
@@ -470,9 +469,18 @@
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
if (mDefaultBackCallback != null) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+ mDefaultBackCallback = null;
}
}
+ /**
+ * Whether this dialog allows to register the default onBackInvokedCallback.
+ * @hide
+ */
+ protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+ return true;
+ }
+
private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";
@@ -697,7 +705,8 @@
if (event.isTracking() && !event.isCanceled()) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
- if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+ if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)
+ || !allowsRegisterDefaultOnBackInvokedCallback()) {
onBackPressed();
return true;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 44068dd..9a5247a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -12783,7 +12783,6 @@
} else {
mBackgroundColor = rawColor;
}
- mProtectionColor = COLOR_INVALID; // filled in at the end
mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
mBackgroundColor, 4.5);
@@ -12800,7 +12799,6 @@
} else {
int[] attrs = {
R.attr.colorSurface,
- R.attr.colorBackgroundFloating,
R.attr.textColorPrimary,
R.attr.textColorSecondary,
R.attr.colorAccent,
@@ -12812,15 +12810,14 @@
};
try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE);
- mProtectionColor = getColor(ta, 1, COLOR_INVALID);
- mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID);
- mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
- mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
- mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
- mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
- mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
- mErrorColor = getColor(ta, 8, COLOR_INVALID);
- mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
+ mPrimaryTextColor = getColor(ta, 1, COLOR_INVALID);
+ mSecondaryTextColor = getColor(ta, 2, COLOR_INVALID);
+ mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
+ mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
+ mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
+ mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID);
+ mErrorColor = getColor(ta, 7, COLOR_INVALID);
+ mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff));
}
mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
mBackgroundColor, nightMode);
@@ -12853,9 +12850,7 @@
}
}
// make sure every color has a valid value
- if (mProtectionColor == COLOR_INVALID) {
- mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f);
- }
+ mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f);
}
/** calculates the contrast color for the non-colorized notifications */
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index c3df17d..529363f 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -344,15 +344,22 @@
// If there is a label for the launcher intent, then use that as it is typically shorter.
// Otherwise, just use the top-level application name.
Intent launchIntent = pm.getLaunchIntentForPackage(mContext.getPackageName());
+ if (launchIntent == null) {
+ return getDefaultCallingApplicationLabel();
+ }
List<ResolveInfo> infos =
pm.queryIntentActivities(
launchIntent, PackageManager.ResolveInfoFlags.of(MATCH_DEFAULT_ONLY));
if (infos.size() > 0) {
return infos.get(0).loadLabel(pm);
}
+ return getDefaultCallingApplicationLabel();
+ }
+
+ private CharSequence getDefaultCallingApplicationLabel() {
return mContext.getApplicationInfo()
.loadSafeLabel(
- pm,
+ mContext.getPackageManager(),
/* ellipsizeDip= */ 0,
TextUtils.SAFE_STRING_FLAG_SINGLE_LINE
| TextUtils.SAFE_STRING_FLAG_TRIM);
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index c2a0062..eedb25b 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -123,7 +123,7 @@
* credential, display a picker when multiple credentials exist, etc.
* Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an
* app different from their own, to be able to get credentials on behalf of that app. They would
- * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
* @param context the context used to launch any UI needed; use an activity context to make sure
@@ -209,9 +209,9 @@
*
* <p>This API doesn't invoke any UI. It only performs the preparation work so that you can
* later launch the remaining get-credential operation (involves UIs) through the {@link
- * #getCredential(PrepareGetCredentialResponse.PendingGetCredentialHandle, Context,
+ * #getCredential(Context, PrepareGetCredentialResponse.PendingGetCredentialHandle,
* CancellationSignal, Executor, OutcomeReceiver)} API which incurs less latency compared to
- * the {@link #getCredential(GetCredentialRequest, Context, CancellationSignal, Executor,
+ * the {@link #getCredential(Context, GetCredentialRequest, CancellationSignal, Executor,
* OutcomeReceiver)} API that executes the whole operation in one call.
*
* @param request the request specifying type(s) of credentials to get from the user
@@ -261,7 +261,7 @@
* storing the new credential, etc.
* Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an
* app different from their own, to be able to get credentials on behalf of that app. They would
- * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
* @param context the context used to launch any UI needed; use an activity context to make sure
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 6baf91d7..ea951a5 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -236,9 +236,10 @@
private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
new CameraExtensionManagerGlobal();
private final Object mLock = new Object();
- private final int PROXY_SERVICE_DELAY_MS = 1000;
+ private final int PROXY_SERVICE_DELAY_MS = 2000;
private InitializerFuture mInitFuture = null;
private ServiceConnection mConnection = null;
+ private int mConnectionCount = 0;
private ICameraExtensionsProxyService mProxy = null;
private boolean mSupportsAdvancedExtensions = false;
@@ -249,6 +250,15 @@
return GLOBAL_CAMERA_MANAGER;
}
+ private void releaseProxyConnectionLocked(Context ctx) {
+ if (mConnection != null ) {
+ ctx.unbindService(mConnection);
+ mConnection = null;
+ mProxy = null;
+ mConnectionCount = 0;
+ }
+ }
+
private void connectToProxyLocked(Context ctx) {
if (mConnection == null) {
Intent intent = new Intent();
@@ -270,7 +280,6 @@
mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName component) {
- mInitFuture.setStatus(false);
mConnection = null;
mProxy = null;
}
@@ -348,23 +357,32 @@
public boolean registerClient(Context ctx, IBinder token) {
synchronized (mLock) {
+ boolean ret = false;
connectToProxyLocked(ctx);
if (mProxy == null) {
return false;
}
+ mConnectionCount++;
try {
- return mProxy.registerClient(token);
+ ret = mProxy.registerClient(token);
} catch (RemoteException e) {
Log.e(TAG, "Failed to initialize extension! Extension service does "
+ " not respond!");
}
+ if (!ret) {
+ mConnectionCount--;
+ }
- return false;
+ if (mConnectionCount <= 0) {
+ releaseProxyConnectionLocked(ctx);
+ }
+
+ return ret;
}
}
- public void unregisterClient(IBinder token) {
+ public void unregisterClient(Context ctx, IBinder token) {
synchronized (mLock) {
if (mProxy != null) {
try {
@@ -372,6 +390,11 @@
} catch (RemoteException e) {
Log.e(TAG, "Failed to de-initialize extension! Extension service does"
+ " not respond!");
+ } finally {
+ mConnectionCount--;
+ if (mConnectionCount <= 0) {
+ releaseProxyConnectionLocked(ctx);
+ }
}
}
}
@@ -446,8 +469,8 @@
/**
* @hide
*/
- public static void unregisterClient(IBinder token) {
- CameraExtensionManagerGlobal.get().unregisterClient(token);
+ public static void unregisterClient(Context ctx, IBinder token) {
+ CameraExtensionManagerGlobal.get().unregisterClient(ctx, token);
}
/**
@@ -578,7 +601,7 @@
}
}
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableList(ret);
@@ -626,7 +649,7 @@
Log.e(TAG, "Failed to query the extension for postview availability! Extension "
+ "service does not respond!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return false;
@@ -722,7 +745,7 @@
+ "service does not respond!");
return Collections.emptyList();
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
}
@@ -791,7 +814,7 @@
+ " not respond!");
return new ArrayList<>();
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
}
@@ -872,7 +895,7 @@
}
}
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -957,7 +980,7 @@
Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
+ " not respond!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return null;
@@ -998,7 +1021,7 @@
Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
+ " not respond!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return false;
@@ -1075,7 +1098,7 @@
} catch (RemoteException e) {
throw new IllegalStateException("Failed to query the available capture request keys!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableSet(ret);
@@ -1155,7 +1178,7 @@
} catch (RemoteException e) {
throw new IllegalStateException("Failed to query the available capture result keys!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index ae700a0..f0fb2f9 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -90,7 +90,7 @@
private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>();
private RequestProcessor mRequestProcessor = new RequestProcessor();
private final int mSessionId;
- private final IBinder mToken;
+ private IBinder mToken = null;
private Surface mClientRepeatingRequestSurface;
private Surface mClientCaptureSurface;
@@ -103,6 +103,8 @@
private boolean mInitialized;
private boolean mSessionClosed;
+ private final Context mContext;
+
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock;
@@ -113,14 +115,9 @@
public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession(
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@NonNull Map<String, CameraCharacteristics> characteristicsMap,
- @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
+ @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId,
+ @NonNull IBinder token)
throws CameraAccessException, RemoteException {
- final IBinder token = new Binder(TAG + " : " + sessionId);
- boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
- if (!success) {
- throw new UnsupportedOperationException("Unsupported extension!");
- }
-
String cameraId = cameraDevice.getId();
CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx,
cameraId, characteristicsMap);
@@ -204,8 +201,9 @@
IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension(
config.getExtension());
extender.init(cameraId, characteristicsMapNative);
- CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender,
- cameraDevice, characteristicsMapNative, repeatingRequestSurface,
+
+ CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
+ extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
burstCaptureSurface, postviewSurface, config.getStateCallback(),
config.getExecutor(), sessionId, token);
@@ -217,13 +215,16 @@
return ret;
}
- private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender,
+ private CameraAdvancedExtensionSessionImpl(Context ctx,
+ @NonNull IAdvancedExtenderImpl extender,
@NonNull CameraDeviceImpl cameraDevice,
Map<String, CameraMetadataNative> characteristicsMap,
@Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
@Nullable Surface postviewSurface,
@NonNull StateCallback callback, @NonNull Executor executor,
- int sessionId, @NonNull IBinder token) {
+ int sessionId,
+ @NonNull IBinder token) {
+ mContext = ctx;
mAdvancedExtender = extender;
mCameraDevice = cameraDevice;
mCharacteristicsMap = characteristicsMap;
@@ -578,12 +579,16 @@
mSessionProcessor = null;
}
- CameraExtensionCharacteristics.unregisterClient(mToken);
- if (mInitialized || (mCaptureSession != null)) {
- notifyClose = true;
- CameraExtensionCharacteristics.releaseSession();
+
+ if (mToken != null) {
+ if (mInitialized || (mCaptureSession != null)) {
+ notifyClose = true;
+ CameraExtensionCharacteristics.releaseSession();
+ }
+ CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
}
mInitialized = false;
+ mToken = null;
for (ImageReader reader : mReaderMap.values()) {
reader.close();
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d3bde4b..181ab2c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -2550,19 +2550,32 @@
HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>(
mPhysicalIdsToChars);
characteristicsMap.put(mCameraId, mCharacteristics);
+ boolean initializationFailed = true;
+ IBinder token = new Binder(TAG + " : " + mNextSessionId++);
try {
+ boolean ret = CameraExtensionCharacteristics.registerClient(mContext, token);
+ if (!ret) {
+ token = null;
+ throw new UnsupportedOperationException("Unsupported extension!");
+ }
+
if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported()) {
mCurrentAdvancedExtensionSession =
CameraAdvancedExtensionSessionImpl.createCameraAdvancedExtensionSession(
this, characteristicsMap, mContext, extensionConfiguration,
- mNextSessionId++);
+ mNextSessionId, token);
} else {
mCurrentExtensionSession = CameraExtensionSessionImpl.createCameraExtensionSession(
this, characteristicsMap, mContext, extensionConfiguration,
- mNextSessionId++);
+ mNextSessionId, token);
}
+ initializationFailed = false;
} catch (RemoteException e) {
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+ } finally {
+ if (initializationFailed && (token != null)) {
+ CameraExtensionCharacteristics.unregisterClient(mContext, token);
+ }
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1db4808..7b7d7de 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -91,7 +91,7 @@
private final Set<CaptureRequest.Key> mSupportedRequestKeys;
private final Set<CaptureResult.Key> mSupportedResultKeys;
private final ExtensionSessionStatsAggregator mStatsAggregator;
- private final IBinder mToken;
+ private IBinder mToken = null;
private boolean mCaptureResultsSupported;
private CameraCaptureSession mCaptureSession = null;
@@ -119,6 +119,8 @@
// will do so internally.
private boolean mInternalRepeatingRequestEnabled = true;
+ private final Context mContext;
+
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock;
@@ -135,14 +137,9 @@
@NonNull Map<String, CameraCharacteristics> characteristicsMap,
@NonNull Context ctx,
@NonNull ExtensionSessionConfiguration config,
- int sessionId)
+ int sessionId,
+ @NonNull IBinder token)
throws CameraAccessException, RemoteException {
- final IBinder token = new Binder(TAG + " : " + sessionId);
- boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
- if (!success) {
- throw new UnsupportedOperationException("Unsupported extension!");
- }
-
String cameraId = cameraDevice.getId();
CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx,
cameraId, characteristicsMap);
@@ -234,6 +231,7 @@
characteristicsMap.get(cameraId).getNativeMetadata());
CameraExtensionSessionImpl session = new CameraExtensionSessionImpl(
+ ctx,
extenders.second,
extenders.first,
supportedPreviewSizes,
@@ -256,7 +254,7 @@
return session;
}
- public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender,
+ public CameraExtensionSessionImpl(Context ctx, @NonNull IImageCaptureExtenderImpl imageExtender,
@NonNull IPreviewExtenderImpl previewExtender,
@NonNull List<Size> previewSizes,
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@@ -269,6 +267,7 @@
@NonNull IBinder token,
@NonNull Set<CaptureRequest.Key> requestKeys,
@Nullable Set<CaptureResult.Key> resultKeys) {
+ mContext = ctx;
mImageExtender = imageExtender;
mPreviewExtender = previewExtender;
mCameraDevice = cameraDevice;
@@ -878,12 +877,15 @@
+ " respond!");
}
- CameraExtensionCharacteristics.unregisterClient(mToken);
- if (mInitialized || (mCaptureSession != null)) {
- notifyClose = true;
- CameraExtensionCharacteristics.releaseSession();
+ if (mToken != null) {
+ if (mInitialized || (mCaptureSession != null)) {
+ notifyClose = true;
+ CameraExtensionCharacteristics.releaseSession();
+ }
+ CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
}
mInitialized = false;
+ mToken = null;
if (mRepeatingRequestImageCallback != null) {
mRepeatingRequestImageCallback.close();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 94bff89..4700720 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -370,8 +370,9 @@
/**
* Returns the default size of the surface associated with the display, or null if the surface
- * is not provided for layer mirroring by SurfaceFlinger.
- * Only used for mirroring started from MediaProjection.
+ * is not provided for layer mirroring by SurfaceFlinger. Size is rotated to reflect the current
+ * display device orientation.
+ * Used for mirroring from MediaProjection, or a physical display based on display flags.
*/
public abstract Point getDisplaySurfaceDefaultSize(int displayId);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 795eb4a..8f653b3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -576,6 +576,12 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
+ /**
+ * Enable the logic to allow hiding the IME caption bar ("fake" IME navigation bar).
+ * @hide
+ */
+ public static final boolean ENABLE_HIDE_IME_CAPTION_BAR = true;
+
LayoutInflater mInflater;
TypedArray mThemeAttrs;
@UnsupportedAppUsage
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 78388ef..c01664e 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -16,6 +16,8 @@
package android.inputmethodservice;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import android.animation.ValueAnimator;
@@ -230,6 +232,16 @@
setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
mDrawLegacyNavigationBarBackground));
+
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
+ if (mNavigationBarFrame != null) {
+ boolean visible = insets.isVisible(captionBar());
+ mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ return view.onApplyWindowInsets(insets);
+ });
+ }
}
private void uninstallNavigationBarFrameIfNecessary() {
@@ -240,6 +252,9 @@
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(mNavigationBarFrame);
}
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setOnApplyWindowInsetsListener(null);
+ }
mNavigationBarFrame = null;
}
@@ -414,7 +429,9 @@
decor.bringChildToFront(mNavigationBarFrame);
}
}
- mNavigationBarFrame.setVisibility(View.VISIBLE);
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ mNavigationBarFrame.setVisibility(View.VISIBLE);
+ }
}
}
@@ -435,6 +452,11 @@
mShouldShowImeSwitcherWhenImeIsShown;
mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+ if (ENABLE_HIDE_IME_CAPTION_BAR) {
+ mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
+ .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight());
+ }
+
if (imeDrawsImeNavBar) {
installNavigationBarFrameIfNecessary();
if (mNavigationBarFrame == null) {
@@ -528,6 +550,16 @@
return drawLegacyNavigationBarBackground;
}
+ /**
+ * Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
+ */
+ private int getImeCaptionBarHeight() {
+ return mImeDrawsImeNavBar
+ ? mService.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_frame_height)
+ : 0;
+ }
+
@Override
public String toDebugString() {
return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 5704dac..e4a09a6 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -79,6 +79,13 @@
@WindowState
private int mWindowState = WindowState.TOKEN_PENDING;
+ @Override
+ protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+ // Do not register OnBackInvokedCallback from Dialog#onStart, InputMethodService will
+ // register CompatOnBackInvokedCallback for input method window.
+ return false;
+ }
+
/**
* Set {@link IBinder} window token to the window.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1235b78..1cf41cf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9975,6 +9975,13 @@
public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory";
/**
+ * Stores a boolean that defines whether the CSD as a feature is enabled or not.
+ * @hide
+ */
+ public static final String AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED =
+ "audio_safe_csd_as_a_feature_enabled";
+
+ /**
* Indicates whether notification display on the lock screen is enabled.
* <p>
* Type: int (0 for false, 1 for true)
@@ -10491,6 +10498,14 @@
"assist_long_press_home_enabled";
/**
+ * Whether press and hold on nav handle can trigger search.
+ *
+ * @hide
+ */
+ public static final String SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED =
+ "search_press_hold_nav_handle_enabled";
+
+ /**
* Control whether Trust Agents are in active unlock or extend unlock mode.
* @hide
*/
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 75640bd..f3b4c6d 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -92,6 +92,7 @@
mapParcel.recycle();
if (buffer != null) {
mRankingMapFd.unmap(buffer);
+ mRankingMapFd.close();
}
}
} else {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 708ebdf..a4989af 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -67,6 +67,7 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
+import com.android.internal.infra.AndroidFuture;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -1702,6 +1703,11 @@
Slog.i(TAG, "onProcessRestarted");
mHandler.sendEmptyMessage(MSG_PROCESS_RESTARTED);
}
+
+ @Override
+ public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
+ throw new UnsupportedOperationException("Hotword cannot access files from the disk.");
+ }
}
void onDetectorRemoteException() {
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index d9ee859..ccf8b67 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -227,6 +227,12 @@
public void stopDetection() {
HotwordDetectionService.this.onStopDetection();
}
+
+ @Override
+ public void registerRemoteStorageService(IDetectorSessionStorageService
+ detectorSessionStorageService) {
+ throw new UnsupportedOperationException("Hotword cannot access files from the disk.");
+ }
};
@Override
diff --git a/core/java/android/service/voice/IDetectorSessionStorageService.aidl b/core/java/android/service/voice/IDetectorSessionStorageService.aidl
new file mode 100644
index 0000000..592373e
--- /dev/null
+++ b/core/java/android/service/voice/IDetectorSessionStorageService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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 android.service.voice;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * @hide
+ */
+oneway interface IDetectorSessionStorageService {
+ /**
+ * Called when a file open request is sent. Only files with the given names under the internal
+ * app storage, i.e., {@link Context#getFilesDir()} can be opened.
+ */
+ void openFile(in String filename, in AndroidFuture future);
+}
diff --git a/core/java/android/service/voice/ISandboxedDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl
index 098536d..c76ac28 100644
--- a/core/java/android/service/voice/ISandboxedDetectionService.aidl
+++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl
@@ -24,6 +24,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.SharedMemory;
+import android.service.voice.IDetectorSessionStorageService;
import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
import android.service.voice.IDspHotwordDetectionCallback;
import android.view.contentcapture.IContentCaptureManager;
@@ -71,4 +72,10 @@
void ping(in IRemoteCallback callback);
void stopDetection();
+
+ /**
+ * Registers the interface stub to talk to the voice interaction service for initialization/
+ * detection unrelated functionalities.
+ */
+ void registerRemoteStorageService(in IDetectorSessionStorageService detectorSessionStorageService);
}
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 7ab4faf..88c2111 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -36,6 +36,7 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.infra.AndroidFuture;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -299,6 +300,11 @@
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
() -> mCallback.onHotwordDetectionServiceRestarted()));
}
+
+ @Override
+ public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
+ throw new UnsupportedOperationException("Hotword cannot access files from the disk.");
+ }
}
/** @hide */
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index cbe7666..d184b1e 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -40,7 +40,12 @@
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.IContentCaptureManager;
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.util.Objects;
+import java.util.concurrent.ExecutionException;
import java.util.function.IntConsumer;
/**
@@ -86,6 +91,8 @@
private ContentCaptureManager mContentCaptureManager;
@Nullable
private IRecognitionServiceManager mIRecognitionServiceManager;
+ @Nullable
+ private IDetectorSessionStorageService mDetectorSessionStorageService;
private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
@@ -154,6 +161,12 @@
public void updateRecognitionServiceManager(IRecognitionServiceManager manager) {
mIRecognitionServiceManager = manager;
}
+
+ @Override
+ public void registerRemoteStorageService(IDetectorSessionStorageService
+ detectorSessionStorageService) {
+ mDetectorSessionStorageService = detectorSessionStorageService;
+ }
};
@Override
@@ -323,4 +336,23 @@
}
}
+ /**
+ * Overrides {@link Context#openFileInput} to read files with the given file names under the
+ * internal app storage of the {@link VoiceInteractionService}, i.e., only files stored in
+ * {@link Context#getFilesDir()} can be opened.
+ */
+ @Override
+ public @Nullable FileInputStream openFileInput(@NonNull String filename) throws
+ FileNotFoundException {
+ try {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ mDetectorSessionStorageService.openFile(filename, future);
+ ParcelFileDescriptor pfd = future.get();
+ return new FileInputStream(pfd.getFileDescriptor());
+ } catch (RemoteException | ExecutionException | InterruptedException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ }
+
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 93b7964..5d7f478 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -25,6 +25,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.content.Context;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.os.Binder;
@@ -37,7 +38,10 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.infra.AndroidFuture;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -58,17 +62,18 @@
private final Callback mCallback;
private final Executor mExecutor;
+ private final Context mContext;
private final IVoiceInteractionManagerService mManagerService;
private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
VisualQueryDetector(
IVoiceInteractionManagerService managerService,
- @NonNull @CallbackExecutor Executor executor,
- Callback callback) {
+ @NonNull @CallbackExecutor Executor executor, Callback callback, Context context) {
mManagerService = managerService;
mCallback = callback;
mExecutor = executor;
mInitializationDelegate = new VisualQueryDetectorInitializationDelegate();
+ mContext = context;
}
/**
@@ -245,7 +250,7 @@
@Override
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
initAndVerifyDetector(options, sharedMemory,
- new InitializationStateListener(mExecutor, mCallback),
+ new InitializationStateListener(mExecutor, mCallback, mContext),
DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
}
@@ -330,9 +335,12 @@
private final Executor mExecutor;
private final Callback mCallback;
- InitializationStateListener(Executor executor, Callback callback) {
+ private final Context mContext;
+
+ InitializationStateListener(Executor executor, Callback callback, Context context) {
this.mExecutor = executor;
this.mCallback = callback;
+ this.mContext = context;
}
@Override
@@ -426,5 +434,22 @@
!TextUtils.isEmpty(errorMessage) ? errorMessage : "Error data is null");
}));
}
+ @Override
+ public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
+ Slog.v(TAG, "BinderCallback#onOpenFile " + filename);
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
+ Slog.v(TAG, "onOpenFile: " + filename);
+ File f = new File(mContext.getFilesDir(), filename);
+ ParcelFileDescriptor pfd = null;
+ try {
+ Slog.d(TAG, "opened a file with ParcelFileDescriptor.");
+ pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
+ } finally {
+ future.complete(pfd);
+ }
+ }));
+ }
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index ab9ae0a..de2a99b 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -965,7 +965,7 @@
}
VisualQueryDetector visualQueryDetector =
- new VisualQueryDetector(mSystemService, executor, callback);
+ new VisualQueryDetector(mSystemService, executor, callback, this);
HotwordDetector visualQueryDetectorInitializationDelegate =
visualQueryDetector.getInitializationDelegate();
mActiveDetectors.add(visualQueryDetectorInitializationDelegate);
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 9656f36..7f313c1 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -38,6 +38,7 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.ref.WeakReference;
@@ -232,39 +233,68 @@
intent,
attributionSource,
new ModelDownloadListener() {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mIsTerminated = false;
+
@Override
public void onProgress(int completedPercent) {
- try {
- listener.onProgress(completedPercent);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ try {
+ listener.onProgress(completedPercent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@Override
public void onSuccess() {
- try {
- listener.onSuccess();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ mIsTerminated = true;
+ try {
+ listener.onSuccess();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@Override
public void onScheduled() {
- try {
- listener.onScheduled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ mIsTerminated = true;
+ try {
+ listener.onScheduled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@Override
public void onError(int error) {
- try {
- listener.onError(error);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ mIsTerminated = true;
+ try {
+ listener.onError(error);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
});
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 4cfec99..9b34007 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -19,7 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
@@ -78,11 +81,17 @@
private int mConnectionCount = 0;
private final InputMethodManager mImm;
+ private final RectF mTempRectF = new RectF();
+
+ private final Region mTempRegion = new Region();
+
+ private final Matrix mTempMatrix = new Matrix();
+
/**
* The handwrite-able View that is currently the target of a hovering stylus pointer. This is
* used to help determine whether the handwriting PointerIcon should be shown in
* {@link #onResolvePointerIcon(Context, MotionEvent)} so that we can reduce the number of calls
- * to {@link #findBestCandidateView(float, float)}.
+ * to {@link #findBestCandidateView(float, float, boolean)}.
*/
@Nullable
private WeakReference<View> mCachedHoverTarget = null;
@@ -189,8 +198,8 @@
final float y = motionEvent.getY(pointerIndex);
if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) {
mState.mExceedHandwritingSlop = true;
- View candidateView =
- findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY);
+ View candidateView = findBestCandidateView(mState.mStylusDownX,
+ mState.mStylusDownY, /* isHover */ false);
if (candidateView != null) {
if (candidateView == getConnectedView()) {
if (!candidateView.hasFocus()) {
@@ -398,13 +407,14 @@
final View cachedHoverTarget = getCachedHoverTarget();
if (cachedHoverTarget != null) {
final Rect handwritingArea = getViewHandwritingArea(cachedHoverTarget);
- if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget)
+ if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget,
+ /* isHover */ true)
&& shouldTriggerStylusHandwritingForView(cachedHoverTarget)) {
return cachedHoverTarget;
}
}
- final View candidateView = findBestCandidateView(hoverX, hoverY);
+ final View candidateView = findBestCandidateView(hoverX, hoverY, /* isHover */ true);
if (candidateView != null) {
mCachedHoverTarget = new WeakReference<>(candidateView);
@@ -434,14 +444,14 @@
* @param y the y coordinates of the stylus event, in the coordinates of the window.
*/
@Nullable
- private View findBestCandidateView(float x, float y) {
+ private View findBestCandidateView(float x, float y, boolean isHover) {
// If the connectedView is not null and do not set any handwriting area, it will check
// whether the connectedView's boundary contains the initial stylus position. If true,
// directly return the connectedView.
final View connectedView = getConnectedView();
if (connectedView != null) {
Rect handwritingArea = getViewHandwritingArea(connectedView);
- if (isInHandwritingArea(handwritingArea, x, y, connectedView)
+ if (isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
&& shouldTriggerStylusHandwritingForView(connectedView)) {
return connectedView;
}
@@ -455,7 +465,7 @@
for (HandwritableViewInfo viewInfo : handwritableViewInfos) {
final View view = viewInfo.getView();
final Rect handwritingArea = viewInfo.getHandwritingArea();
- if (!isInHandwritingArea(handwritingArea, x, y, view)
+ if (!isInHandwritingArea(handwritingArea, x, y, view, isHover)
|| !shouldTriggerStylusHandwritingForView(view)) {
continue;
}
@@ -551,15 +561,48 @@
* Return true if the (x, y) is inside by the given {@link Rect} with the View's
* handwriting bounds with offsets applied.
*/
- private static boolean isInHandwritingArea(@Nullable Rect handwritingArea,
- float x, float y, View view) {
+ private boolean isInHandwritingArea(@Nullable Rect handwritingArea,
+ float x, float y, View view, boolean isHover) {
if (handwritingArea == null) return false;
- return contains(handwritingArea, x, y,
+ if (!contains(handwritingArea, x, y,
view.getHandwritingBoundsOffsetLeft(),
view.getHandwritingBoundsOffsetTop(),
view.getHandwritingBoundsOffsetRight(),
- view.getHandwritingBoundsOffsetBottom());
+ view.getHandwritingBoundsOffsetBottom())) {
+ return false;
+ }
+
+ // The returned handwritingArea computed by ViewParent#getChildVisibleRect didn't consider
+ // the case where a view is stacking on top of the editor. (e.g. DrawerLayout, popup)
+ // We must check the hit region of the editor again, and avoid the case where another
+ // view on top of the editor is handling MotionEvents.
+ ViewParent parent = view.getParent();
+ if (parent == null) {
+ return true;
+ }
+
+ Region region = mTempRegion;
+ mTempRegion.set(0, 0, view.getWidth(), view.getHeight());
+ Matrix matrix = mTempMatrix;
+ matrix.reset();
+ if (!parent.getChildLocalHitRegion(view, region, matrix, isHover)) {
+ return false;
+ }
+
+ // It's not easy to extend the region by the given handwritingBoundsOffset. Instead, we
+ // create a rectangle surrounding the motion event location and check if this rectangle
+ // overlaps with the hit region of the editor.
+ float left = x - view.getHandwritingBoundsOffsetRight();
+ float top = y - view.getHandwritingBoundsOffsetBottom();
+ float right = Math.max(x + view.getHandwritingBoundsOffsetLeft(), left + 1);
+ float bottom = Math.max(y + view.getHandwritingBoundsOffsetTop(), top + 1);
+ RectF rectF = mTempRectF;
+ rectF.set(left, top, right, bottom);
+ matrix.mapRect(rectF);
+
+ return region.op(Math.round(rectF.left), Math.round(rectF.top),
+ Math.round(rectF.right), Math.round(rectF.bottom), Region.Op.INTERSECT);
}
/**
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4ecfc40..c6d8bd1 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,10 +16,12 @@
package android.view;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.InsetsControllerProto.CONTROL;
import static android.view.InsetsControllerProto.STATE;
import static android.view.InsetsSource.ID_IME;
+import static android.view.InsetsSource.ID_IME_CAPTION_BAR;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.FIRST;
import static android.view.WindowInsets.Type.LAST;
@@ -40,6 +42,7 @@
import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -652,6 +655,7 @@
private int mLastWindowingMode;
private boolean mStartingAnimation;
private int mCaptionInsetsHeight = 0;
+ private int mImeCaptionBarInsetsHeight = 0;
private boolean mAnimationsDisabled;
private boolean mCompatSysUiVisibilityStaled;
@@ -693,6 +697,9 @@
if (!CAPTION_ON_SHELL && source1.getType() == captionBar()) {
return;
}
+ if (source1.getId() == ID_IME_CAPTION_BAR) {
+ return;
+ }
// Don't change the indexes of the sources while traversing. Remove it later.
mPendingRemoveIndexes.add(index1);
@@ -823,6 +830,9 @@
if (mFrame.equals(frame)) {
return;
}
+ if (mImeCaptionBarInsetsHeight != 0) {
+ setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+ }
mHost.notifyInsetsChanged();
mFrame.set(frame);
}
@@ -1007,6 +1017,12 @@
// Ensure to update all existing source consumers
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+ if (consumer.getId() == ID_IME_CAPTION_BAR) {
+ // The inset control for the IME caption bar will never be dispatched
+ // by the server.
+ continue;
+ }
+
final InsetsSourceControl control = mTmpControlArray.get(consumer.getId());
if (control != null) {
controllableTypes |= control.getType();
@@ -1499,7 +1515,8 @@
continue;
}
final InsetsSourceControl control = consumer.getControl();
- if (control != null && control.getLeash() != null) {
+ if (control != null
+ && (control.getLeash() != null || control.getId() == ID_IME_CAPTION_BAR)) {
controls.put(control.getId(), new InsetsSourceControl(control));
typesReady |= consumer.getType();
}
@@ -1885,6 +1902,35 @@
}
@Override
+ public void setImeCaptionBarInsetsHeight(int height) {
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ return;
+ }
+ Rect newFrame = new Rect(mFrame.left, mFrame.bottom - height, mFrame.right, mFrame.bottom);
+ InsetsSource source = mState.peekSource(ID_IME_CAPTION_BAR);
+ if (mImeCaptionBarInsetsHeight != height
+ || (source != null && !newFrame.equals(source.getFrame()))) {
+ mImeCaptionBarInsetsHeight = height;
+ if (mImeCaptionBarInsetsHeight != 0) {
+ mState.getOrCreateSource(ID_IME_CAPTION_BAR, captionBar())
+ .setFrame(newFrame);
+ getSourceConsumer(ID_IME_CAPTION_BAR, captionBar()).setControl(
+ new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(),
+ null /* leash */, false /* initialVisible */,
+ new Point(), Insets.NONE),
+ new int[1], new int[1]);
+ } else {
+ mState.removeSource(ID_IME_CAPTION_BAR);
+ InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR);
+ if (sourceConsumer != null) {
+ sourceConsumer.setControl(null, new int[1], new int[1]);
+ }
+ }
+ mHost.notifyInsetsChanged();
+ }
+ }
+
+ @Override
public void setSystemBarsBehavior(@Behavior int behavior) {
mHost.setSystemBarsBehavior(behavior);
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 6441186..ff009ed 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -20,6 +20,7 @@
import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.ime;
import android.annotation.IntDef;
@@ -47,6 +48,9 @@
/** The insets source ID of IME */
public static final int ID_IME = createId(null, 0, ime());
+ /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */
+ static final int ID_IME_CAPTION_BAR =
+ InsetsSource.createId(null /* owner */, 1 /* index */, captionBar());
/**
* Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't
@@ -215,8 +219,12 @@
// During drag-move and drag-resizing, the caption insets position may not get updated
// before the app frame get updated. To layout the app content correctly during drag events,
// we always return the insets with the corresponding height covering the top.
+ // However, with the "fake" IME navigation bar treated as a caption bar, we return the
+ // insets with the corresponding height the bottom.
if (getType() == WindowInsets.Type.captionBar()) {
- return Insets.of(0, frame.height(), 0, 0);
+ return getId() == ID_IME_CAPTION_BAR
+ ? Insets.of(0, 0, 0, frame.height())
+ : Insets.of(0, frame.height(), 0, 0);
}
// Checks for whether there is shared edge with insets for 0-width/height window.
final boolean hasIntersection = relativeFrame.isEmpty()
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index e8f62fc..a4cbc52 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@
private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
+ private int mImeCaptionBarInsetsHeight = 0;
private WindowInsetsAnimationControlListener mLoggingListener;
private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
@@ -91,6 +92,11 @@
}
@Override
+ public void setImeCaptionBarInsetsHeight(int height) {
+ mImeCaptionBarInsetsHeight = height;
+ }
+
+ @Override
public void setSystemBarsBehavior(int behavior) {
if (mReplayedInsetsController != null) {
mReplayedInsetsController.setSystemBarsBehavior(behavior);
@@ -168,6 +174,9 @@
if (mCaptionInsetsHeight != 0) {
controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
}
+ if (mImeCaptionBarInsetsHeight != 0) {
+ controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+ }
if (mAnimationsDisabled) {
controller.setAnimationsDisabled(true);
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 1b1098d..7bdff8c 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -7361,6 +7361,90 @@
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ if (!child.hasIdentityMatrix()) {
+ matrix.preConcat(child.getInverseMatrix());
+ }
+
+ final int dx = child.mLeft - mScrollX;
+ final int dy = child.mTop - mScrollY;
+ matrix.preTranslate(-dx, -dy);
+
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ // Map the bounds of this view into the region's coordinates and clip the region.
+ final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
+ rect.set(0, 0, width, height);
+ matrix.mapRect(rect);
+
+ boolean notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom), Region.Op.INTERSECT);
+
+ if (isHover) {
+ HoverTarget target = mFirstHoverTarget;
+ boolean childIsHit = false;
+ while (target != null) {
+ final HoverTarget next = target.next;
+ if (target.child == child) {
+ childIsHit = true;
+ break;
+ }
+ target = next;
+ }
+ if (!childIsHit) {
+ target = mFirstHoverTarget;
+ while (notEmpty && target != null) {
+ final HoverTarget next = target.next;
+ final View hoveredView = target.child;
+
+ rect.set(hoveredView.mLeft, hoveredView.mTop, hoveredView.mRight,
+ hoveredView.mBottom);
+ matrix.mapRect(rect);
+ notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE);
+ target = next;
+ }
+ }
+ } else {
+ TouchTarget target = mFirstTouchTarget;
+ boolean childIsHit = false;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (target.child == child) {
+ childIsHit = true;
+ break;
+ }
+ target = next;
+ }
+ if (!childIsHit) {
+ target = mFirstTouchTarget;
+ while (notEmpty && target != null) {
+ final TouchTarget next = target.next;
+ final View touchedView = target.child;
+
+ rect.set(touchedView.mLeft, touchedView.mTop, touchedView.mRight,
+ touchedView.mBottom);
+ matrix.mapRect(rect);
+ notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE);
+ target = next;
+ }
+ }
+ }
+
+ if (notEmpty && mParent != null) {
+ notEmpty = mParent.getChildLocalHitRegion(this, region, matrix, isHover);
+ }
+ return notEmpty;
+ }
+
+
private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) {
final int[] locationInWindow = new int[2];
view.getLocationInWindow(locationInWindow);
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 1020d2e..54bc348 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
@@ -686,6 +687,36 @@
}
/**
+ * Compute the region where the child can receive the {@link MotionEvent}s from the root view.
+ *
+ * <p> Given region where the child will accept {@link MotionEvent}s.
+ * Modify the region to the unblocked region where the child can receive the
+ * {@link MotionEvent}s from the view root.
+ * </p>
+ *
+ * <p> The given region is always clipped by the bounds of the parent views. When there are
+ * on-going {@link MotionEvent}s, this method also makes use of the event dispatching results to
+ * determine whether a sibling view will also block the child's hit region.
+ * </p>
+ *
+ * @param child a child View, whose hit region we want to compute.
+ * @param region the initial hit region where the child view will handle {@link MotionEvent}s,
+ * defined in the child coordinates. Will be overwritten to the result hit region.
+ * @param matrix the matrix that maps the given child view's coordinates to the region
+ * coordinates. It will be modified to a matrix that maps window coordinates to
+ * the result region's coordinates.
+ * @param isHover if true it will return the hover events' hit region, otherwise it will
+ * return the touch events' hit region.
+ * @return true if the returned region is not empty.
+ * @hide
+ */
+ default boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ region.setEmpty();
+ return false;
+ }
+
+ /**
* Unbuffered dispatch has been requested by a child of this view parent.
* This method is called by the View hierarchy to signal ancestors that a View needs to
* request unbuffered dispatch.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6edf0e2..c1ce5e0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -127,6 +127,7 @@
import android.graphics.PorterDuff;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
@@ -2393,6 +2394,22 @@
}
@Override
+ public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ if (child != mView) {
+ throw new IllegalArgumentException("child " + child + " is not the root view "
+ + mView + " managed by this ViewRootImpl");
+ }
+
+ RectF rectF = new RectF(0, 0, mWidth, mHeight);
+ matrix.mapRect(rectF);
+ // Note: don't apply scroll offset, because we want to know its
+ // visibility in the virtual canvas being given to the view hierarchy.
+ return region.op(Math.round(rectF.left), Math.round(rectF.top),
+ Math.round(rectF.right), Math.round(rectF.bottom), Region.Op.INTERSECT);
+ }
+
+ @Override
public void bringChildToFront(View child) {
}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index bc0bab7..cc2cd79 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -250,6 +250,16 @@
void setCaptionInsetsHeight(int height);
/**
+ * Sets the insets height for the IME caption bar, which corresponds to the
+ * "fake" IME navigation bar.
+ *
+ * @param height the insets height of the IME caption bar.
+ * @hide
+ */
+ default void setImeCaptionBarInsetsHeight(int height) {
+ }
+
+ /**
* Controls the behavior of system bars.
*
* @param behavior Determines how the bars behave when being hidden by the application.
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index 3801188..ba87caa 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -22,6 +22,7 @@
import android.service.voice.HotwordRejectedResult;
import android.service.voice.SoundTriggerFailure;
import android.service.voice.VisualQueryDetectionServiceFailure;
+import com.android.internal.infra.AndroidFuture;
/**
* @hide
@@ -113,4 +114,9 @@
/** Called when the hotword detection process is restarted */
void onProcessRestarted();
+
+ /**
+ * Called when a file open request is sent.
+ */
+ void onOpenFile(in String filename, in AndroidFuture future);
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index a5d287c..481a2fb 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -137,6 +137,7 @@
optional SettingProto gesture_setup_complete = 9 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto search_press_hold_nav_handle_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Assist assist = 7;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 10cf353..7d1253c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7770,8 +7770,9 @@
android:process=":ui">
</activity>
<activity android:name="com.android.internal.app.PlatLogoActivity"
- android:theme="@style/Theme.Wallpaper.NoTitleBar.Fullscreen"
+ android:theme="@style/Theme.NoTitleBar.Fullscreen"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+ android:enableOnBackInvokedCallback="true"
android:icon="@drawable/platlogo"
android:process=":ui">
</activity>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d86d53f..75f1251 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3013,14 +3013,15 @@
on the headphone/microphone jack. When false use the older uevent framework. -->
<bool name="config_useDevInputEventForAudioJack">false</bool>
- <!-- Whether safe headphone volume is enabled or not (country specific). -->
+ <!-- Whether safe headphone hearing is enforced by any regulation (e.g.
+ EN50332-3, EN50332-2) or not (country specific). -->
<bool name="config_safe_media_volume_enabled">true</bool>
- <!-- Whether safe headphone sound dosage warning is enabled or not
- (country specific). This value should only be overlaid to true
- when a vendor supports offload and has the HAL sound dose
- interfaces implemented. Otherwise, this can lead to a compliance
- issue with the safe hearing standards EN50332-3 and IEC62368-1.
+ <!-- Whether safe headphone sound dosage warning is enabled or not.
+ This value should only be overlaid to true when a vendor supports
+ offload and has the HAL sound dose interfaces implemented.
+ Otherwise, this can lead to a compliance issue with the safe
+ hearing standards EN50332-3 and IEC62368-1.
-->
<bool name="config_safe_sound_dosage_enabled">false</bool>
@@ -6084,6 +6085,9 @@
<!-- Default value for Settings.ASSIST_TOUCH_GESTURE_ENABLED -->
<bool name="config_assistTouchGestureEnabledDefault">true</bool>
+ <!-- Default value for Settings.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED -->
+ <bool name="config_searchPressHoldNavHandleEnabledDefault">true</bool>
+
<!-- The maximum byte size of the information contained in the bundle of
HotwordDetectedResult. -->
<integer translatable="false" name="config_hotwordDetectedResultMaxBundleSize">0</integer>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 33c18c2..9c018c30 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1889,22 +1889,22 @@
<!-- Content description which should be used for the fingerprint icon. -->
<string name="fingerprint_icon_content_description">Fingerprint icon</string>
+ <!-- Notification name shown when the system requires the user to set up device unlock. [CHAR LIMIT=NONE] -->
+ <string name="device_unlock_notification_name">Device unlock</string>
+ <!-- Notification title shown when the system suggests the user to set up another way to unlock. [CHAR LIMIT=NONE] -->
+ <string name="alternative_unlock_setup_notification_title">Try another way to unlock</string>
+ <!-- Notification content shown when the system suggests the user to enroll their face. [CHAR LIMIT=NONE] -->
+ <string name="alternative_face_setup_notification_content">Use Face Unlock when your fingerprint isn\'t recognized, like when your fingers are wet</string>
+ <!-- Notification content shown when the system suggests the user to enroll their fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="alternative_fp_setup_notification_content">Use Fingerprint Unlock when your face isn\'t recognized, like when there\'s not enough light</string>
<!-- Notification name shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
<string name="face_recalibrate_notification_name">Face Unlock</string>
<!-- Notification title shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
<string name="face_recalibrate_notification_title">Issue with Face Unlock</string>
<!-- Notification content shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
<string name="face_recalibrate_notification_content">Tap to delete your face model, then add your face again</string>
- <!-- Title of a notification that directs the user to set up Face Unlock by enrolling their face. [CHAR LIMIT=NONE] -->
- <string name="face_setup_notification_title">Set up Face Unlock</string>
- <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
- <string name="face_setup_notification_content">Unlock your phone by looking at it</string>
<!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
<string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
- <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
- <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
- <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
- <string name="fingerprint_setup_notification_content">Tap to add a fingerprint</string>
<!-- Notification name shown when the system requires the user to re-calibrate their fingerprint. [CHAR LIMIT=NONE] -->
<string name="fingerprint_recalibrate_notification_name">Fingerprint Unlock</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cfcabf3..72b76c2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2584,6 +2584,12 @@
<!-- Biometric FRR config -->
<java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+ <!-- Biometric FRR notification messages -->
+ <java-symbol type="string" name="device_unlock_notification_name" />
+ <java-symbol type="string" name="alternative_unlock_setup_notification_title" />
+ <java-symbol type="string" name="alternative_face_setup_notification_content" />
+ <java-symbol type="string" name="alternative_fp_setup_notification_content" />
+
<!-- Device credential strings for BiometricManager -->
<java-symbol type="string" name="screen_lock_app_setting_name" />
<java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -2624,8 +2630,6 @@
<java-symbol type="string" name="fingerprint_recalibrate_notification_name" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_title" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_content" />
- <java-symbol type="string" name="fingerprint_setup_notification_title" />
- <java-symbol type="string" name="fingerprint_setup_notification_content" />
<java-symbol type="string" name="fingerprint_error_power_pressed" />
<!-- Fingerprint config -->
@@ -2693,7 +2697,6 @@
<java-symbol type="string" name="face_authenticated_no_confirmation_required" />
<java-symbol type="string" name="face_authenticated_confirmation_required" />
<java-symbol type="string" name="face_error_security_update_required" />
- <java-symbol type="string" name="face_setup_notification_title" />
<java-symbol type="string" name="config_biometric_prompt_ui_package" />
<java-symbol type="array" name="config_biometric_sensors" />
@@ -4861,6 +4864,8 @@
<java-symbol type="bool" name="config_assistLongPressHomeEnabledDefault" />
<java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" />
+ <java-symbol type="bool" name="config_searchPressHoldNavHandleEnabledDefault" />
+
<java-symbol type="integer" name="config_hotwordDetectedResultMaxBundleSize" />
<java-symbol type="dimen" name="config_wallpaperDimAmount" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 31755ef..a358c4f 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1749,6 +1749,15 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
</intent-filter>
</activity>
+
+ <activity android:name="android.view.ViewGroupTestActivity"
+ android:label="ViewGroup Test"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/res/layout/viewgroup_test.xml b/core/tests/coretests/res/layout/viewgroup_test.xml
new file mode 100644
index 0000000..04f4f52
--- /dev/null
+++ b/core/tests/coretests/res/layout/viewgroup_test.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<!-- Demonstrates adding/removing views from ViewGroup. See corresponding Java code. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <EditText
+ android:id="@+id/view"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:text="Hello World!"
+ android:background="#2F00FF00" />
+ <EditText
+ android:id="@+id/view_scale"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:scaleX="0.5"
+ android:scaleY="2"
+ android:transformPivotX="0dp"
+ android:transformPivotY="0dp"
+ android:text="Hello World!"
+ android:background="#2F00FF00" />
+ <EditText
+ android:id="@+id/view_translate"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:translationX="10dp"
+ android:translationY="20dp"
+ android:text="Hello World!"
+ android:background="#2F00FF00" />
+ <FrameLayout
+ android:layout_width="20dp"
+ android:layout_height="10dp">
+ <EditText
+ android:id="@+id/view_overlap_bottom"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:text="Hello World!"/>
+ <Button
+ android:id="@+id/view_overlap_top"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="20dp"
+ android:layout_height="10dp">
+ <EditText
+ android:id="@+id/view_cover_bottom"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:text="Hello World!"/>
+ <Button
+ android:id="@+id/view_cover_top"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ </FrameLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index a84ac55..55ded9c 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -136,7 +136,11 @@
NotificationListenerService.RankingMap retrievedRankings =
retrievedRankingUpdate.getRankingMap();
assertNotNull(retrievedRankings);
- assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
+ // The rankingUpdate file descriptor is only non-null in the new path.
+ if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+ SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
+ }
NotificationListenerService.Ranking retrievedRanking =
new NotificationListenerService.Ranking();
assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
diff --git a/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java b/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java
new file mode 100644
index 0000000..60a0a2a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 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 android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import android.graphics.Matrix;
+import android.graphics.Region;
+import android.platform.test.annotations.Presubmit;
+import android.widget.LinearLayout;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.SmallTest;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test basic functions of ViewGroup.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ViewGroupTest
+ */
+@Presubmit
+@SmallTest
+public class ViewGroupGetChildLocalHitRegionTest {
+ @Rule
+ public ActivityScenarioRule<ViewGroupTestActivity> mScenarioRule =
+ new ActivityScenarioRule<>(ViewGroupTestActivity.class);
+
+ private LinearLayout mRoot;
+ private final int[] mRootLocation = new int[2];
+
+ @Before
+ public void setup() {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ mRoot = activity.findViewById(R.id.linear_layout);
+ mRoot.getLocationInWindow(mRootLocation);
+ });
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion() {
+ assertGetChildLocalHitRegion(R.id.view);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_withScale() {
+ assertGetChildLocalHitRegion(R.id.view_scale);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_withTranslate() {
+ assertGetChildLocalHitRegion(R.id.view_translate);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_overlap_noMotionEvent() {
+ assertGetChildLocalHitRegion(R.id.view_overlap_bottom);
+ }
+ @Test
+ public void testGetChildLocalHitRegion_overlap_withMotionEvent() {
+ // In this case, view_cover_bottom is partially covered by the view_cover_top.
+ // The returned region is the bounds of the bottom view subtract the bounds of the top view.
+ assertGetChildLocalHitRegion(R.id.view_overlap_top, R.id.view_overlap_bottom);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_cover_withMotionEvent() {
+ // In this case, view_cover_bottom is completely covered by the view_cover_top.
+ // The returned region is expected to be empty.
+ assertGetChildLocalHitRegionEmpty(R.id.view_cover_top, R.id.view_cover_bottom);
+ }
+
+ private void injectMotionEvent(View view, boolean isHover) {
+ int[] location = new int[2];
+ view.getLocationInWindow(location);
+
+ float x = location[0] + view.getWidth() / 2f;
+ float y = location[1] + view.getHeight() / 2f;
+
+ int action = isHover ? MotionEvent.ACTION_HOVER_ENTER : MotionEvent.ACTION_DOWN;
+ MotionEvent motionEvent = MotionEvent.obtain(/* downtime= */ 0, /* eventTime= */ 0, action,
+ x, y, /* pressure= */ 0, /* size= */ 0, /* metaState= */ 0,
+ /* xPrecision= */ 1, /* yPrecision= */ 1, /* deviceId= */0, /* edgeFlags= */0);
+
+ View rootView = view.getRootView();
+ rootView.dispatchPointerEvent(motionEvent);
+ }
+
+ private void assertGetChildLocalHitRegion(int viewId) {
+ assertGetChildLocalHitRegion(viewId, /* isHover= */ true);
+ assertGetChildLocalHitRegion(viewId, /* isHover= */ false);
+ }
+
+ /**
+ * Assert ViewParent#getChildLocalHitRegion for a single view.
+ * @param viewId the viewId of the tested view.
+ * @param isHover if true, check the hit region of the hover events. Otherwise, check the hit
+ * region of the touch events.
+ */
+ private void assertGetChildLocalHitRegion(int viewId, boolean isHover) {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ View view = activity.findViewById(viewId);
+
+ Matrix actualMatrix = new Matrix();
+ Region actualRegion = new Region(0, 0, view.getWidth(), view.getHeight());
+ boolean actualNotEmpty = view.getParent()
+ .getChildLocalHitRegion(view, actualRegion, actualMatrix, isHover);
+
+ int[] windowLocation = new int[2];
+ view.getLocationInWindow(windowLocation);
+ Matrix expectMatrix = new Matrix();
+ expectMatrix.preScale(1 / view.getScaleX(), 1 / view.getScaleY());
+ expectMatrix.preTranslate(-windowLocation[0], -windowLocation[1]);
+
+ Region expectRegion = new Region(0, 0, view.getWidth(), view.getHeight());
+
+ assertThat(actualNotEmpty).isTrue();
+ assertThat(actualMatrix).isEqualTo(expectMatrix);
+ assertThat(actualRegion).isEqualTo(expectRegion);
+ });
+ }
+
+ private void assertGetChildLocalHitRegion(int viewIdTop, int viewIdBottom) {
+ assertGetChildLocalHitRegion(viewIdTop, viewIdBottom, /* isHover= */ true);
+ assertGetChildLocalHitRegion(viewIdTop, viewIdBottom, /* isHover= */ false);
+ }
+
+ /**
+ * Assert ViewParent#getChildLocalHitRegion of a view that is covered by another view. It will
+ * inject {@link MotionEvent}s to the view on top first and then get the hit region of the
+ * bottom view.
+ *
+ * @param viewIdTop the view id of the test view on top.
+ * @param viewIdBottom the view id of the test view at the bottom.
+ * @param isHover if true, check the hit region of the hover events. Otherwise, check the hit
+ * region of the touch events.
+ */
+ private void assertGetChildLocalHitRegion(int viewIdTop, int viewIdBottom, boolean isHover) {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ View viewTop = activity.findViewById(viewIdTop);
+ View viewBottom = activity.findViewById(viewIdBottom);
+
+ injectMotionEvent(viewTop, isHover);
+
+ Matrix actualMatrix = new Matrix();
+ Region actualRegion = new Region(0, 0, viewBottom.getWidth(), viewBottom.getHeight());
+ boolean actualNotEmpty = viewBottom.getParent()
+ .getChildLocalHitRegion(viewBottom, actualRegion, actualMatrix, isHover);
+
+ int[] windowLocation = new int[2];
+ viewBottom.getLocationInWindow(windowLocation);
+ Matrix expectMatrix = new Matrix();
+ expectMatrix.preTranslate(-windowLocation[0], -windowLocation[1]);
+
+ Region expectRegion = new Region(0, 0, viewBottom.getWidth(), viewBottom.getHeight());
+ expectRegion.op(0, 0, viewTop.getWidth(), viewTop.getHeight(), Region.Op.DIFFERENCE);
+
+ assertThat(actualNotEmpty).isTrue();
+ assertThat(actualMatrix).isEqualTo(expectMatrix);
+ assertThat(actualRegion).isEqualTo(expectRegion);
+ });
+ }
+
+ private void assertGetChildLocalHitRegionEmpty(int viewIdTop, int viewIdBottom) {
+ assertGetChildLocalHitRegionEmpty(viewIdTop, viewIdBottom, /* isHover= */ true);
+ assertGetChildLocalHitRegionEmpty(viewIdTop, viewIdBottom, /* isHover= */ false);
+ }
+
+ /**
+ * Assert ViewParent#getChildLocalHitRegion returns an empty region for a view that is
+ * completely covered by another view. It will inject {@link MotionEvent}s to the view on top
+ * first and then get the hit region of the
+ * bottom view.
+ *
+ * @param viewIdTop the view id of the test view on top.
+ * @param viewIdBottom the view id of the test view at the bottom.
+ * @param isHover if true, check the hit region of the hover events. Otherwise, check the hit
+ * region of the touch events.
+ */
+ private void assertGetChildLocalHitRegionEmpty(int viewIdTop, int viewIdBottom,
+ boolean isHover) {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ View viewTop = activity.findViewById(viewIdTop);
+ View viewBottom = activity.findViewById(viewIdBottom);
+
+ injectMotionEvent(viewTop, isHover);
+
+ Region actualRegion = new Region(0, 0, viewBottom.getWidth(), viewBottom.getHeight());
+ boolean actualNotEmpty = viewBottom.getParent()
+ .getChildLocalHitRegion(viewBottom, actualRegion, new Matrix(), isHover);
+
+ assertThat(actualNotEmpty).isFalse();
+ assertThat(actualRegion.isEmpty()).isTrue();
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewGroupTestActivity.java b/core/tests/coretests/src/android/view/ViewGroupTestActivity.java
new file mode 100644
index 0000000..b94bda5
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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 android.view;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+public class ViewGroupTestActivity extends Activity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.viewgroup_test);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
index 388a996..b4c72ca 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
@@ -21,7 +21,9 @@
import android.app.Instrumentation;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.Region;
import android.view.View;
import android.view.ViewGroup;
@@ -45,7 +47,7 @@
float handwritingBoundsOffsetRight, float handwritingBoundsOffsetBottom) {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final Context context = instrumentation.getTargetContext();
- // mock a parent so that HandwritingInitiator can get
+ // mock a parent so that HandwritingInitiator can get visible rect and hit region.
final ViewGroup parent = new ViewGroup(context) {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
@@ -56,6 +58,14 @@
r.set(handwritingArea);
return true;
}
+
+ @Override
+ public boolean getChildLocalHitRegion(View child, Region region, Matrix matrix,
+ boolean isHover) {
+ matrix.reset();
+ region.set(handwritingArea);
+ return true;
+ }
};
View view = spy(new View(context));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt
new file mode 100644
index 0000000..642dacc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUiEventLogger.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.common.pip
+
+import android.app.TaskInfo
+import android.content.pm.PackageManager
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/**
+ * Helper class that ends PiP log to UiEvent, see also go/uievent
+ */
+class PipUiEventLogger(
+ private val mUiEventLogger: UiEventLogger,
+ private val mPackageManager: PackageManager
+) {
+ private var mPackageName: String? = null
+ private var mPackageUid = INVALID_PACKAGE_UID
+ fun setTaskInfo(taskInfo: TaskInfo?) {
+ if (taskInfo?.topActivity != null) {
+ // safe because topActivity is guaranteed non-null here
+ mPackageName = taskInfo.topActivity!!.packageName
+ mPackageUid = getUid(mPackageName!!, taskInfo.userId)
+ } else {
+ mPackageName = null
+ mPackageUid = INVALID_PACKAGE_UID
+ }
+ }
+
+ /**
+ * Sends log via UiEvent, reference go/uievent for how to debug locally
+ */
+ fun log(event: PipUiEventEnum?) {
+ if (mPackageName == null || mPackageUid == INVALID_PACKAGE_UID) {
+ return
+ }
+ mUiEventLogger.log(event!!, mPackageUid, mPackageName)
+ }
+
+ private fun getUid(packageName: String, userId: Int): Int {
+ var uid = INVALID_PACKAGE_UID
+ try {
+ uid = mPackageManager.getApplicationInfoAsUser(
+ packageName, 0 /* ApplicationInfoFlags */, userId
+ ).uid
+ } catch (e: PackageManager.NameNotFoundException) {
+ // do nothing.
+ }
+ return uid
+ }
+
+ /**
+ * Enums for logging the PiP events to UiEvent
+ */
+ enum class PipUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Activity enters picture-in-picture mode")
+ PICTURE_IN_PICTURE_ENTER(603),
+
+ @UiEvent(doc = "Activity enters picture-in-picture mode with auto-enter-pip API")
+ PICTURE_IN_PICTURE_AUTO_ENTER(1313),
+
+ @UiEvent(doc = "Activity enters picture-in-picture mode from content-pip API")
+ PICTURE_IN_PICTURE_ENTER_CONTENT_PIP(1314),
+
+ @UiEvent(doc = "Expands from picture-in-picture to fullscreen")
+ PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
+
+ @UiEvent(doc = "Removes picture-in-picture by tap close button")
+ PICTURE_IN_PICTURE_TAP_TO_REMOVE(605),
+
+ @UiEvent(doc = "Removes picture-in-picture by drag to dismiss area")
+ PICTURE_IN_PICTURE_DRAG_TO_REMOVE(606),
+
+ @UiEvent(doc = "Shows picture-in-picture menu")
+ PICTURE_IN_PICTURE_SHOW_MENU(607),
+
+ @UiEvent(doc = "Hides picture-in-picture menu")
+ PICTURE_IN_PICTURE_HIDE_MENU(608),
+
+ @UiEvent(
+ doc = "Changes the aspect ratio of picture-in-picture window. This is inherited" +
+ " from previous Tron-based logging and currently not in use."
+ )
+ PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609),
+
+ @UiEvent(doc = "User resize of the picture-in-picture window")
+ PICTURE_IN_PICTURE_RESIZE(610),
+
+ @UiEvent(doc = "User unstashed picture-in-picture")
+ PICTURE_IN_PICTURE_STASH_UNSTASHED(709),
+
+ @UiEvent(doc = "User stashed picture-in-picture to the left side")
+ PICTURE_IN_PICTURE_STASH_LEFT(710),
+
+ @UiEvent(doc = "User stashed picture-in-picture to the right side")
+ PICTURE_IN_PICTURE_STASH_RIGHT(711),
+
+ @UiEvent(doc = "User taps on the settings button in PiP menu")
+ PICTURE_IN_PICTURE_SHOW_SETTINGS(933),
+
+ @UiEvent(doc = "Closes PiP with app-provided close action")
+ PICTURE_IN_PICTURE_CUSTOM_CLOSE(1058);
+
+ override fun getId(): Int {
+ return mId
+ }
+ }
+
+ companion object {
+ private const val INVALID_PACKAGE_UID = -1
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index ef93a33..be1b9b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common.split;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -60,7 +61,8 @@
public static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW,
+ WINDOWING_MODE_FREEFORM};
/** Flag applied to a transition change to identify it as a divider bar for animation. */
public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 1a84f4b..8df89bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -20,6 +20,7 @@
import android.app.ActivityTaskManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.SystemProperties;
import android.view.IWindowManager;
@@ -56,6 +57,7 @@
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -319,6 +321,17 @@
return Optional.empty();
}
+ //
+ // PiP (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+ PackageManager packageManager) {
+ return new PipUiEventLogger(uiEventLogger, packageManager);
+ }
+
//
// Bubbles (optional feature)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 9bf973f..5fd4f24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -32,6 +32,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
@@ -49,7 +50,6 @@
import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
index e8fae24..55a810a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -17,15 +17,12 @@
package com.android.wm.shell.dagger.pip;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Handler;
-import com.android.internal.logging.UiEventLogger;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipUiEventLogger;
import dagger.Module;
import dagger.Provides;
@@ -49,11 +46,4 @@
static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
return new PipSurfaceTransactionHelper(context);
}
-
- @WMSingleton
- @Provides
- static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
- PackageManager packageManager) {
- return new PipUiEventLogger(uiEventLogger, packageManager);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index c7c6e8a..8dec4ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
+import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.PipTransition;
@@ -26,9 +27,9 @@
/**
* Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
- * the successor of its sibling {@link Pip1SharedModule}.
+ * the successor of its sibling {@link Pip1Module}.
*/
-@Module
+@Module(includes = WMShellBaseModule.class)
public abstract class Pip2Module {
@WMSingleton
@Provides
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 80ffbb0..4e332be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -30,6 +30,7 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
@@ -43,7 +44,6 @@
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 1d46e75..633f627 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -18,6 +18,7 @@
import android.R
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -301,6 +302,24 @@
}
}
+ /** Move a desktop app to split screen. */
+ fun moveToSplit(task: RunningTaskInfo) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToSplit taskId=%d",
+ task.taskId
+ )
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
+ wct.setBounds(task.token, null)
+ wct.setDensityDpi(task.token, getDefaultDensityDpi())
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/**
* The second part of the animated move to desktop transition, called after
* {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 19c60c2..296857b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -82,6 +82,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
deleted file mode 100644
index 3e5a19b..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.pip;
-
-import android.app.TaskInfo;
-import android.content.pm.PackageManager;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-
-/**
- * Helper class that ends PiP log to UiEvent, see also go/uievent
- */
-public class PipUiEventLogger {
-
- private static final int INVALID_PACKAGE_UID = -1;
-
- private final UiEventLogger mUiEventLogger;
- private final PackageManager mPackageManager;
-
- private String mPackageName;
- private int mPackageUid = INVALID_PACKAGE_UID;
-
- public PipUiEventLogger(UiEventLogger uiEventLogger, PackageManager packageManager) {
- mUiEventLogger = uiEventLogger;
- mPackageManager = packageManager;
- }
-
- public void setTaskInfo(TaskInfo taskInfo) {
- if (taskInfo != null && taskInfo.topActivity != null) {
- mPackageName = taskInfo.topActivity.getPackageName();
- mPackageUid = getUid(mPackageName, taskInfo.userId);
- } else {
- mPackageName = null;
- mPackageUid = INVALID_PACKAGE_UID;
- }
- }
-
- /**
- * Sends log via UiEvent, reference go/uievent for how to debug locally
- */
- public void log(PipUiEventEnum event) {
- if (mPackageName == null || mPackageUid == INVALID_PACKAGE_UID) {
- return;
- }
- mUiEventLogger.log(event, mPackageUid, mPackageName);
- }
-
- private int getUid(String packageName, int userId) {
- int uid = INVALID_PACKAGE_UID;
- try {
- uid = mPackageManager.getApplicationInfoAsUser(
- packageName, 0 /* ApplicationInfoFlags */, userId).uid;
- } catch (PackageManager.NameNotFoundException e) {
- // do nothing.
- }
- return uid;
- }
-
- /**
- * Enums for logging the PiP events to UiEvent
- */
- public enum PipUiEventEnum implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "Activity enters picture-in-picture mode")
- PICTURE_IN_PICTURE_ENTER(603),
-
- @UiEvent(doc = "Activity enters picture-in-picture mode with auto-enter-pip API")
- PICTURE_IN_PICTURE_AUTO_ENTER(1313),
-
- @UiEvent(doc = "Activity enters picture-in-picture mode from content-pip API")
- PICTURE_IN_PICTURE_ENTER_CONTENT_PIP(1314),
-
- @UiEvent(doc = "Expands from picture-in-picture to fullscreen")
- PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
-
- @UiEvent(doc = "Removes picture-in-picture by tap close button")
- PICTURE_IN_PICTURE_TAP_TO_REMOVE(605),
-
- @UiEvent(doc = "Removes picture-in-picture by drag to dismiss area")
- PICTURE_IN_PICTURE_DRAG_TO_REMOVE(606),
-
- @UiEvent(doc = "Shows picture-in-picture menu")
- PICTURE_IN_PICTURE_SHOW_MENU(607),
-
- @UiEvent(doc = "Hides picture-in-picture menu")
- PICTURE_IN_PICTURE_HIDE_MENU(608),
-
- @UiEvent(doc = "Changes the aspect ratio of picture-in-picture window. This is inherited"
- + " from previous Tron-based logging and currently not in use.")
- PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609),
-
- @UiEvent(doc = "User resize of the picture-in-picture window")
- PICTURE_IN_PICTURE_RESIZE(610),
-
- @UiEvent(doc = "User unstashed picture-in-picture")
- PICTURE_IN_PICTURE_STASH_UNSTASHED(709),
-
- @UiEvent(doc = "User stashed picture-in-picture to the left side")
- PICTURE_IN_PICTURE_STASH_LEFT(710),
-
- @UiEvent(doc = "User stashed picture-in-picture to the right side")
- PICTURE_IN_PICTURE_STASH_RIGHT(711),
-
- @UiEvent(doc = "User taps on the settings button in PiP menu")
- PICTURE_IN_PICTURE_SHOW_SETTINGS(933),
-
- @UiEvent(doc = "Closes PiP with app-provided close action")
- PICTURE_IN_PICTURE_CUSTOM_CLOSE(1058);
-
- private final int mId;
-
- PipUiEventEnum(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 5e1b6be..c93d850 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -38,12 +38,12 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index b872267..f25110a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -123,14 +123,6 @@
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
- private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
-
- @VisibleForTesting
- void setEnablePipKeepClearAlgorithm(boolean value) {
- mEnablePipKeepClearAlgorithm = value;
- }
-
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -166,10 +158,6 @@
// early bail out if the change was caused by keyguard showing up
return;
}
- if (!mEnablePipKeepClearAlgorithm) {
- // early bail out if the keep clear areas feature is disabled
- return;
- }
if (mPipBoundsState.isStashed()) {
// don't move when stashed
return;
@@ -187,10 +175,6 @@
}
private void updatePipPositionForKeepClearAreas() {
- if (!mEnablePipKeepClearAlgorithm) {
- // early bail out if the keep clear areas feature is disabled
- return;
- }
if (mIsKeyguardShowingOrAnimating) {
// early bail out if the change was caused by keyguard showing up
return;
@@ -343,19 +327,17 @@
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mPipDisplayLayoutState.getDisplayId() == displayId) {
- if (mEnablePipKeepClearAlgorithm) {
- mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+ mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
- mMainExecutor.removeCallbacks(
- mMovePipInResponseToKeepClearAreasChangeCallback);
- mMainExecutor.executeDelayed(
- mMovePipInResponseToKeepClearAreasChangeCallback,
- PIP_KEEP_CLEAR_AREAS_DELAY);
+ mMainExecutor.removeCallbacks(
+ mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.executeDelayed(
+ mMovePipInResponseToKeepClearAreasChangeCallback,
+ PIP_KEEP_CLEAR_AREAS_DELAY);
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
- restricted, unrestricted);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
+ restricted, unrestricted);
}
}
};
@@ -660,25 +642,9 @@
// there's a keyguard present
return;
}
- int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChangedUncheck(mDisplayController
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()),
false /* saveRestoreSnapFraction */);
- int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- if (!mEnablePipKeepClearAlgorithm) {
- // offset PiP to adjust for bottom inset change
- int pipTop = mPipBoundsState.getBounds().top;
- int diff = newMaxMovementBound - oldMaxMovementBound;
- if (diff < 0 && pipTop > newMaxMovementBound) {
- // bottom inset has increased, move PiP up if it is too low
- mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
- newMaxMovementBound - pipTop);
- }
- if (diff > 0 && oldMaxMovementBound == pipTop) {
- // bottom inset has decreased, move PiP down if it was by the edge
- mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
- }
- }
}
});
@@ -947,14 +913,8 @@
* Sets both shelf visibility and its height.
*/
private void setShelfHeight(boolean visible, int height) {
- if (mEnablePipKeepClearAlgorithm) {
- // turn this into Launcher keep clear area registration instead
- setLauncherKeepClearAreaHeight(visible, height);
- return;
- }
- if (!mIsKeyguardShowingOrAnimating) {
- setShelfHeightLocked(visible, height);
- }
+ // turn this into Launcher keep clear area registration instead
+ setLauncherKeepClearAreaHeight(visible, height);
}
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
@@ -1015,16 +975,10 @@
private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
int launcherRotation, Rect hotseatKeepClearArea) {
-
- if (mEnablePipKeepClearAlgorithm) {
- // preemptively add the keep clear area for Hotseat, so that it is taken into account
- // when calculating the entry destination bounds of PiP window
- mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
- hotseatKeepClearArea);
- } else {
- int shelfHeight = hotseatKeepClearArea.height();
- setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
- }
+ // preemptively add the keep clear area for Hotseat, so that it is taken into account
+ // when calculating the entry destination bounds of PiP window
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
+ hotseatKeepClearArea);
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index da455f8..4e75847 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -38,7 +38,7 @@
import com.android.wm.shell.common.bubbles.DismissCircleView;
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import kotlin.Unit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 779c539..837426a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -66,7 +66,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index abe2db0..4e687dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -46,11 +46,11 @@
import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index ab65c9e..bfba4b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -34,7 +34,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.Size;
import android.view.DisplayCutout;
@@ -51,13 +50,13 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
@@ -73,14 +72,6 @@
private static final String TAG = "PipTouchHandler";
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
- private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
-
- @VisibleForTesting
- void setEnablePipKeepClearAlgorithm(boolean value) {
- mEnablePipKeepClearAlgorithm = value;
- }
-
// Allow PIP to resize to a slightly bigger state upon touch
private boolean mEnableResize;
private final Context mContext;
@@ -430,48 +421,6 @@
mIsImeShowing ? mImeOffset : 0,
!mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
- // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
- // occluded by the IME or shelf.
- if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
- // Defer the update of the current movement bounds until after the user finishes
- // touching the screen
- } else if (mEnablePipKeepClearAlgorithm) {
- // Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height
- // now are accounted for in the keep clear algorithm calculations
- } else {
- final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
- final Rect toMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
- toMovementBounds, mIsImeShowing ? mImeHeight : 0);
- final int prevBottom = mPipBoundsState.getMovementBounds().bottom
- - mMovementBoundsExtraOffsets;
- // This is to handle landscape fullscreen IMEs, don't apply the extra offset in this
- // case
- final int toBottom = toMovementBounds.bottom < toMovementBounds.top
- ? toMovementBounds.bottom
- : toMovementBounds.bottom - extraOffset;
-
- if (isExpanded) {
- curBounds.set(mPipBoundsState.getExpandedBounds());
- mPipBoundsAlgorithm.getSnapAlgorithm().applySnapFraction(curBounds,
- toMovementBounds, mSavedSnapFraction);
- }
-
- if (prevBottom < toBottom) {
- // The movement bounds are expanding
- if (curBounds.top > prevBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- } else if (prevBottom > toBottom) {
- // The movement bounds are shrinking
- if (curBounds.top > toBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- }
- }
- }
-
// Update the movement bounds after doing the calculations based on the old movement bounds
// above
mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 4819f66..dbec607 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -25,6 +25,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -35,7 +36,6 @@
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2be7a49..29fff03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -71,6 +71,7 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -200,6 +201,19 @@
@Override
public void setSplitScreenController(SplitScreenController splitScreenController) {
mSplitScreenController = splitScreenController;
+ mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ if (visible) {
+ DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
+ if (decor != null && DesktopModeStatus.isActive(mContext)
+ && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
+ mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+ }
+ }
+ }
+ });
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 1e3fe42..248d665 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -53,6 +53,7 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 0ae2908..05c6ba9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -76,7 +76,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -329,21 +328,7 @@
}
@Test
- public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
- mPipController.setEnablePipKeepClearAlgorithm(false);
- final int displayId = 1;
- final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
-
- mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
- displayId, Set.of(keepClearArea), Set.of());
-
- verify(mMockPipBoundsState, never()).setKeepClearAreas(Mockito.anySet(), Mockito.anySet());
- }
-
- @Test
- public void onKeepClearAreasChanged_featureEnabled_updatesPipBoundsState() {
- mPipController.setEnablePipKeepClearAlgorithm(true);
+ public void onKeepClearAreasChanged_updatesPipBoundsState() {
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 689b5c5..12b4f3e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -37,6 +37,7 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -45,7 +46,6 @@
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUiEventLogger;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 852183c..314f195d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -34,6 +33,7 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -42,7 +42,6 @@
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -174,16 +173,4 @@
verify(mPipResizeGestureHandler, times(1))
.updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight());
}
-
- @Test
- public void updateMovementBounds_withImeAdjustment_movesPip() {
- mPipTouchHandler.setEnablePipKeepClearAlgorithm(false);
- mFromImeAdjustment = true;
- mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
-
- mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
- mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
-
- verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt());
- }
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e8c9d0d..c3087bc 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6915,7 +6915,10 @@
/**
* @hide
- * Returns whether CSD is enabled and supported by the HAL on this device.
+ * Returns whether CSD is enabled and supported by the current active audio module HAL.
+ * This method will return {@code false) for setups in which CSD as a feature is available
+ * (see {@link AudioManager#isCsdAsAFeatureAvailable()}) and not enabled (see
+ * {@link AudioManager#isCsdAsAFeatureEnabled()}).
*/
@TestApi
@RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
@@ -6929,6 +6932,49 @@
/**
* @hide
+ * Returns whether CSD as a feature can be manipulated by a client. This method
+ * returns {@code true} in countries where there isn't a safe hearing regulation
+ * enforced.
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public boolean isCsdAsAFeatureAvailable() {
+ try {
+ return getService().isCsdAsAFeatureAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Returns {@code true} if the client has enabled CSD. This function should only
+ * be called if {@link AudioManager#isCsdAsAFeatureAvailable()} returns {@code true}.
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public boolean isCsdAsAFeatureEnabled() {
+ try {
+ return getService().isCsdAsAFeatureEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Enables/disables the CSD feature. This function should only be called if
+ * {@link AudioManager#isCsdAsAFeatureAvailable()} returns {@code true}.
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void setCsdAsAFeatureEnabled(boolean csdToggleValue) {
+ try {
+ getService().setCsdAsAFeatureEnabled(csdToggleValue);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Describes an audio device that has not been categorized with a specific
* audio type.
*/
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 180c7fd..9d62e37 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -323,6 +323,15 @@
boolean isCsdEnabled();
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ boolean isCsdAsAFeatureAvailable();
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ boolean isCsdAsAFeatureEnabled();
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ oneway void setCsdAsAFeatureEnabled(boolean csdToggleValue);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType);
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 9ab84d2..f90a17a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -45,6 +45,7 @@
private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
+ public static final int BATTERY_LEVEL_UNKNOWN = -1;
public static final int CHARGING_UNKNOWN = -1;
public static final int CHARGING_SLOWLY = 0;
public static final int CHARGING_REGULAR = 1;
@@ -186,12 +187,13 @@
/** Gets the battery level from the intent. */
public static int getBatteryLevel(Intent batteryChangedIntent) {
if (batteryChangedIntent == null) {
- return -1; /*invalid battery level*/
+ return BATTERY_LEVEL_UNKNOWN;
}
- final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ final int level =
+ batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN);
final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
return scale == 0
- ? -1 /*invalid battery level*/
+ ? BATTERY_LEVEL_UNKNOWN
: Math.round((level / (float) scale) * 100f);
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3efb41d..c2dbf98 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -240,6 +240,7 @@
Settings.Secure.HEARING_AID_CALL_ROUTING,
Settings.Secure.HEARING_AID_MEDIA_ROUTING,
Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
- Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED
+ Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+ Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index e5f7f88..a49461e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -198,6 +198,7 @@
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a83bfda..ef4e84f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1926,6 +1926,9 @@
dumpSetting(s, p,
Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
SecureSettingsProto.Assist.LONG_PRESS_HOME_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
+ SecureSettingsProto.Assist.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED);
p.end(assistToken);
final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9b8e473..91c72b5 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -703,7 +703,8 @@
Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
Settings.Secure.ASSIST_STRUCTURE_ENABLED,
Settings.Secure.ATTENTIVE_TIMEOUT,
- Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user
+ Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user
+ Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user
Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT,
Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 889c026..dd71dfa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -48,6 +48,7 @@
Column(
modifier =
modifier
+ .element(key = Notifications.Elements.Notifications)
.fillMaxWidth()
.defaultMinSize(minHeight = 300.dp)
.clip(RoundedCornerShape(32.dp))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
index 38712b0..291617f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -1,11 +1,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.scene.ui.composable.QuickSettings
fun TransitionBuilder.goneToQuickSettingsTransition() {
spec = tween(durationMillis = 500)
- fade(QuickSettings.rootElementKey)
+ translate(QuickSettings.rootElementKey, Edge.Top, true)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 1d57c1a..45df2b1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -1,11 +1,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.scene.ui.composable.Shade
fun TransitionBuilder.goneToShadeTransition() {
spec = tween(durationMillis = 500)
- fade(Shade.rootElementKey)
+ translate(Shade.rootElementKey, Edge.Top, true)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
index 9a8a3e2..e63bc4e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -1,11 +1,12 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.scene.ui.composable.QuickSettings
fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
spec = tween(durationMillis = 500)
- fade(QuickSettings.rootElementKey)
+ translate(QuickSettings.rootElementKey, Edge.Top, true)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index 6c7964b..21a10b1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -10,6 +10,5 @@
spec = tween(durationMillis = 500)
translate(Notifications.Elements.Notifications, Edge.Bottom)
- fade(Notifications.Elements.Notifications)
timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index d65edae..0fa4ebd 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -409,6 +409,18 @@
scope.launch(bgDispatcher) { mutateSetting { it.copy(seedColor = value) } }
}
+ // Returns currentClockId if clock is connected, otherwise DEFAULT_CLOCK_ID. Since this
+ // is dependent on which clocks are connected, it may change when a clock is installed or
+ // removed from the device (unlike currentClockId).
+ // TODO: Merge w/ CurrentClockId when we convert to a flow. We shouldn't need both behaviors.
+ val activeClockId: String
+ get() {
+ if (!availableClocks.containsKey(currentClockId)) {
+ return DEFAULT_CLOCK_ID
+ }
+ return currentClockId
+ }
+
init {
// Register default clock designs
for (clock in defaultClockProvider.getClocks()) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e539c95..b28920c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -62,9 +62,10 @@
private val burmeseLineSpacing =
resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+ protected var onSecondaryDisplay: Boolean = false
override val events: DefaultClockEvents
- override val config = ClockConfig()
+ override val config = ClockConfig(DEFAULT_CLOCK_ID)
init {
val parent = FrameLayout(ctx)
@@ -142,6 +143,11 @@
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
recomputePadding(targetRegion)
}
+
+ override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {
+ this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay
+ recomputePadding(null)
+ }
}
open fun recomputePadding(targetRegion: Rect?) {}
@@ -182,13 +188,19 @@
override fun recomputePadding(targetRegion: Rect?) {
// We center the view within the targetRegion instead of within the parent
// view by computing the difference and adding that to the padding.
- val parent = view.parent
- val yDiff =
- if (targetRegion != null && parent is View && parent.isLaidOut())
- targetRegion.centerY() - parent.height / 2f
- else 0f
val lp = view.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
+ lp.topMargin =
+ if (onSecondaryDisplay) {
+ // On the secondary display we don't want any additional top/bottom margin.
+ 0
+ } else {
+ val parent = view.parent
+ val yDiff =
+ if (targetRegion != null && parent is View && parent.isLaidOut())
+ targetRegion.centerY() - parent.height / 2f
+ else 0f
+ (-0.5f * view.bottom + yDiff).toInt()
+ }
view.setLayoutParams(lp)
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index d962732..e2f4793 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -177,6 +177,9 @@
* targetRegion is relative to the parent view.
*/
fun onTargetRegionChanged(targetRegion: Rect?)
+
+ /** Called to notify the clock about its display. */
+ fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
}
/** Tick rates for clocks */
@@ -196,6 +199,8 @@
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig(
+ val id: String,
+
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
new file mode 100644
index 0000000..593f507f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/presentation"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.keyguard.KeyguardStatusView
+ android:id="@+id/clock"
+ android:layout_width="410dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <include
+ android:id="@+id/keyguard_clock_container"
+ layout="@layout/keyguard_clock_switch"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </com.android.keyguard.KeyguardStatusView>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index 4a9d41f..b83f15a 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,6 +14,4 @@
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="3dp"
- android:insetRight="3dp"
android:drawable="@drawable/ic_speaker_mute" />
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 12f13e9..3a15ae4 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -127,6 +127,8 @@
android:gravity="center_vertical"
android:paddingStart="@dimen/shade_header_system_icons_padding_start"
android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
+ android:paddingTop="@dimen/shade_header_system_icons_padding_top"
+ android:paddingBottom="@dimen/shade_header_system_icons_padding_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="@id/clock">
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index 9af46c5..3796415 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -64,8 +64,7 @@
android:layout_height="wrap_content"
android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
style="@style/TextAppearance.Dialog.Body.Message"
- android:gravity="start"
- android:lineHeight="@dimen/screenrecord_warning_line_height"/>
+ android:gravity="start"/>
<!-- Buttons -->
<LinearLayout
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
index 78cd718..39ec09b 100644
--- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -34,8 +34,8 @@
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
android:contentDescription="@string/screenshot_dismiss_work_profile">
<ImageView
- android:layout_width="16dp"
- android:layout_height="16dp"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
android:layout_gravity="center"
android:background="@drawable/circular_background"
android:backgroundTint="?androidprv:attr/materialColorSurfaceContainerHigh"
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index d85e012..915dcdb 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -82,6 +82,8 @@
<!-- start padding is smaller to account for status icon margins coming from drawable itself -->
<dimen name="shade_header_system_icons_padding_start">3dp</dimen>
<dimen name="shade_header_system_icons_padding_end">4dp</dimen>
+ <dimen name="shade_header_system_icons_padding_top">2dp</dimen>
+ <dimen name="shade_header_system_icons_padding_bottom">2dp</dimen>
<!-- Lockscreen shade transition values -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 55978e6..3ada7c2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -496,9 +496,10 @@
<dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
<dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
<dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
- <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen>
<dimen name="shade_header_system_icons_padding_start">0dp</dimen>
<dimen name="shade_header_system_icons_padding_end">0dp</dimen>
+ <dimen name="shade_header_system_icons_padding_top">0dp</dimen>
+ <dimen name="shade_header_system_icons_padding_bottom">0dp</dimen>
<!-- The top margin of the panel that holds the list of notifications.
On phones it's always 0dp but it's overridden in Car UI
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 2ec6180..fe61c46 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -56,7 +56,7 @@
<Constraint android:id="@+id/shade_header_system_icons">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/shade_header_system_icons_height_large_screen"
+ android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/privacy_container"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index c844db7..77f6d03 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -28,8 +28,6 @@
import java.lang.annotation.RetentionPolicy;
public final class InteractionJankMonitorWrapper {
- private static final String TAG = "JankMonitorWrapper";
-
// Launcher journeys.
public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
@@ -37,6 +35,8 @@
InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
public static final int CUJ_APP_CLOSE_TO_HOME =
InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+ public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK =
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
public static final int CUJ_APP_CLOSE_TO_PIP =
InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
public static final int CUJ_QUICK_SWITCH =
@@ -68,6 +68,7 @@
CUJ_APP_LAUNCH_FROM_RECENTS,
CUJ_APP_LAUNCH_FROM_ICON,
CUJ_APP_CLOSE_TO_HOME,
+ CUJ_APP_CLOSE_TO_HOME_FALLBACK,
CUJ_APP_CLOSE_TO_PIP,
CUJ_QUICK_SWITCH,
CUJ_APP_LAUNCH_FROM_WIDGET,
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
new file mode 100644
index 0000000..899cad89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.keyguard
+
+import android.app.Presentation
+import android.content.Context
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import com.android.keyguard.dagger.KeyguardStatusViewComponent
+import com.android.systemui.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** [Presentation] shown in connected displays while on keyguard. */
+class ConnectedDisplayKeyguardPresentation
+@AssistedInject
+constructor(
+ @Assisted display: Display,
+ context: Context,
+ private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+) :
+ Presentation(
+ context,
+ display,
+ R.style.Theme_SystemUI_KeyguardPresentation,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+ ) {
+
+ private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+ private lateinit var clock: KeyguardStatusView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(
+ LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
+ )
+ val window = window ?: error("no window available.")
+
+ // Logic to make the lock screen fullscreen
+ window.decorView.systemUiVisibility =
+ (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
+ window.attributes.fitInsetsTypes = 0
+ window.isNavigationBarContrastEnforced = false
+ window.navigationBarColor = Color.TRANSPARENT
+
+ clock = findViewById(R.id.clock)
+ keyguardStatusViewController =
+ keyguardStatusViewComponentFactory.build(clock).keyguardStatusViewController.apply {
+ setDisplayedOnSecondaryDisplay()
+ init()
+ }
+ }
+
+ /** [ConnectedDisplayKeyguardPresentation] factory. */
+ @AssistedFactory
+ interface Factory {
+ /** Creates a new [Presentation] for the given [display]. */
+ fun create(
+ display: Display,
+ ): ConnectedDisplayKeyguardPresentation
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index b4f1956..5d0e8f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,10 +1,5 @@
package com.android.keyguard;
-import static android.view.View.ALPHA;
-import static android.view.View.SCALE_X;
-import static android.view.View.SCALE_Y;
-import static android.view.View.TRANSLATION_Y;
-
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
@@ -44,6 +39,7 @@
public class KeyguardClockSwitch extends RelativeLayout {
private static final String TAG = "KeyguardClockSwitch";
+ public static final String MISSING_CLOCK_ID = "CLOCK_MISSING";
private static final long CLOCK_OUT_MILLIS = 133;
private static final long CLOCK_IN_MILLIS = 167;
@@ -145,6 +141,13 @@
updateStatusArea(/* animate= */false);
}
+ /** Sets whether the large clock is being shown on a connected display. */
+ public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
+ if (mClock != null) {
+ mClock.getLargeClock().getEvents().onSecondaryDisplayChanged(onSecondaryDisplay);
+ }
+ }
+
/**
* Enable or disable split shade specific positioning
*/
@@ -190,6 +193,14 @@
return mLogBuffer;
}
+ /** Returns the id of the currently rendering clock */
+ public String getClockId() {
+ if (mClock == null) {
+ return MISSING_CLOCK_ID;
+ }
+ return mClock.getConfig().getId();
+ }
+
void setClock(ClockController clock, int statusBarState) {
mClock = clock;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 21d8c0a..8108076 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -104,6 +104,7 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+ private boolean mShownOnSecondaryDisplay = false;
private boolean mOnlyClock = false;
private boolean mIsActiveDreamLockscreenHosted = false;
private FeatureFlags mFeatureFlags;
@@ -185,8 +186,18 @@
}
/**
- * Mostly used for alternate displays, limit the information shown
+ * When set, limits the information shown in an external display.
*/
+ public void setShownOnSecondaryDisplay(boolean shownOnSecondaryDisplay) {
+ mShownOnSecondaryDisplay = shownOnSecondaryDisplay;
+ }
+
+ /**
+ * Mostly used for alternate displays, limit the information shown
+ *
+ * @deprecated use {@link KeyguardClockSwitchController#setShownOnSecondaryDisplay}
+ */
+ @Deprecated
public void setOnlyClock(boolean onlyClock) {
mOnlyClock = onlyClock;
}
@@ -221,6 +232,15 @@
}
}
+ private void hideSliceViewAndNotificationIconContainer() {
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ ksv.setVisibility(View.GONE);
+
+ View nic = mView.findViewById(
+ R.id.left_aligned_notification_icon_container);
+ nic.setVisibility(View.GONE);
+ }
+
@Override
protected void onViewAttached() {
mClockRegistry.registerClockChangeListener(mClockChangedListener);
@@ -233,13 +253,15 @@
mKeyguardDateWeatherViewInvisibility =
mView.getResources().getInteger(R.integer.keyguard_date_weather_view_invisibility);
- if (mOnlyClock) {
- View ksv = mView.findViewById(R.id.keyguard_slice_view);
- ksv.setVisibility(View.GONE);
+ if (mShownOnSecondaryDisplay) {
+ mView.setLargeClockOnSecondaryDisplay(true);
+ displayClock(LARGE, /* animate= */ false);
+ hideSliceViewAndNotificationIconContainer();
+ return;
+ }
- View nic = mView.findViewById(
- R.id.left_aligned_notification_icon_container);
- nic.setVisibility(View.GONE);
+ if (mOnlyClock) {
+ hideSliceViewAndNotificationIconContainer();
return;
}
updateAodIcons();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 9f21a31..1c5a575 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -43,16 +43,19 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import dagger.Lazy;
+
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
@SysUISingleton
public class KeyguardDisplayManager {
@@ -64,6 +67,9 @@
private final DisplayTracker mDisplayTracker;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+ private final ConnectedDisplayKeyguardPresentation.Factory
+ mConnectedDisplayKeyguardPresentationFactory;
+ private final FeatureFlags mFeatureFlags;
private final Context mContext;
private boolean mShowing;
@@ -105,7 +111,10 @@
@Main Executor mainExecutor,
@UiBackground Executor uiBgExecutor,
DeviceStateHelper deviceStateHelper,
- KeyguardStateController keyguardStateController) {
+ KeyguardStateController keyguardStateController,
+ ConnectedDisplayKeyguardPresentation.Factory
+ connectedDisplayKeyguardPresentationFactory,
+ FeatureFlags featureFlags) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
@@ -115,6 +124,8 @@
mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
mDeviceStateHelper = deviceStateHelper;
mKeyguardStateController = keyguardStateController;
+ mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
+ mFeatureFlags = featureFlags;
}
private boolean isKeyguardShowable(Display display) {
@@ -185,8 +196,12 @@
return false;
}
- KeyguardPresentation createPresentation(Display display) {
- return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+ Presentation createPresentation(Display display) {
+ if (mFeatureFlags.isEnabled(Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION)) {
+ return mConnectedDisplayKeyguardPresentationFactory.create(display);
+ } else {
+ return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 04692c4..9f3908a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -27,6 +27,7 @@
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
import android.app.ActivityManager;
@@ -370,8 +371,12 @@
@Override
public void onOrientationChanged(int orientation) {
- KeyguardSecurityContainerController.this
+ if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ // TODO(b/295603468)
+ // Fix reinflation of views when flag is enabled.
+ KeyguardSecurityContainerController.this
.onDensityOrFontScaleOrOrientationChanged();
+ }
}
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 757022d..c314586 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -187,6 +187,11 @@
mConfigurationController.removeCallback(mConfigurationListener);
}
+ /** Sets the StatusView as shown on an external display. */
+ public void setDisplayedOnSecondaryDisplay() {
+ mKeyguardClockSwitchController.setShownOnSecondaryDisplay(true);
+ }
+
/**
* Called in notificationPanelViewController to avoid leak
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f73a602..64c4eec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -107,6 +107,7 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LetterboxModule;
+import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -184,6 +185,7 @@
MediaProjectionModule.class,
MediaProjectionTaskSwitcherModule.class,
MotionToolModule.class,
+ NotificationIconAreaControllerModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index aec7974..f0bd4f0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -115,7 +115,7 @@
// TODO(b/292213543): Tracking Bug
@JvmField
val NOTIFICATION_GROUP_EXPANSION_CHANGE =
- unreleasedFlag("notification_group_expansion_change", teamfood = false)
+ unreleasedFlag("notification_group_expansion_change", teamfood = true)
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -516,11 +516,6 @@
val ENABLE_FLING_TO_DISMISS_PIP =
sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_pip", default = true)
- @Keep
- @JvmField
- val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- sysPropBooleanFlag("persist.wm.debug.enable_pip_keep_clear_algorithm", default = true)
-
// TODO(b/256873975): Tracking Bug
@JvmField
@Keep
@@ -542,13 +537,6 @@
teamfood = false
)
- // TODO(b/198643358): Tracking bug
- @Keep
- @JvmField
- val ENABLE_PIP_SIZE_LARGE_SCREEN =
- sysPropBooleanFlag("persist.wm.debug.enable_pip_size_large_screen", default = true)
-
-
// TODO(b/293252410) : Tracking Bug
@JvmField
val LOCKSCREEN_ENABLE_LANDSCAPE =
@@ -632,6 +620,10 @@
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
+ /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
+ @JvmField
+ val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot")
+
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc")
@@ -773,6 +765,10 @@
@JvmField
val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration")
+ /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
+ @JvmField
+ val ENABLE_CLOCK_KEYGUARD_PRESENTATION = unreleasedFlag("enable_clock_keyguard_presentation")
+
/** Enable the Compose implementation of the PeopleSpaceActivity. */
@JvmField
val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space")
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
new file mode 100644
index 0000000..3f2f67d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import javax.inject.Inject
+
+/**
+ * Sends key events to the appropriate interactors and then acts upon key events that haven't
+ * already been handled but should be handled by SystemUI.
+ */
+@SysUISingleton
+class KeyEventInteractor
+@Inject
+constructor(
+ private val backActionInteractor: BackActionInteractor,
+ private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+) {
+ fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
+ return true
+ }
+
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_BACK -> {
+ if (event.handleAction()) {
+ backActionInteractor.onBackRequested()
+ }
+ return true
+ }
+ }
+ return false
+ }
+
+ fun interceptMediaKey(event: KeyEvent): Boolean {
+ return keyguardKeyEventInteractor.interceptMediaKey(event)
+ }
+
+ fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
+ }
+
+ companion object {
+ // Most actions shouldn't be handled on the down event and instead handled on subsequent
+ // key events like ACTION_UP.
+ fun KeyEvent.handleAction(): Boolean {
+ return action != KeyEvent.ACTION_DOWN
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
new file mode 100644
index 0000000..e501ece
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 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.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import android.media.AudioManager
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+/** Handles key events arriving when the keyguard is showing or device is dozing. */
+@SysUISingleton
+class KeyguardKeyEventInteractor
+@Inject
+constructor(
+ private val context: Context,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val shadeController: ShadeController,
+ private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
+ private val backActionInteractor: BackActionInteractor,
+) {
+
+ fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ if (statusBarStateController.isDozing) {
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_VOLUME_UP -> return dispatchVolumeKeyEvent(event)
+ }
+ }
+
+ if (event.handleAction()) {
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
+ KeyEvent.KEYCODE_SPACE,
+ KeyEvent.KEYCODE_ENTER ->
+ if (isDeviceInteractive()) {
+ return collapseShadeLockedOrShowPrimaryBouncer()
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * While IME is active and a BACK event is detected, check with {@link
+ * StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event should be
+ * handled before routing to IME, in order to prevent the user from having to hit back twice to
+ * exit bouncer.
+ */
+ fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ when (event.keyCode) {
+ KeyEvent.KEYCODE_BACK ->
+ if (
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
+ statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()
+ ) {
+ return backActionInteractor.onBackRequested()
+ }
+ }
+ return false
+ }
+
+ fun interceptMediaKey(event: KeyEvent): Boolean {
+ return statusBarStateController.state == StatusBarState.KEYGUARD &&
+ statusBarKeyguardViewManager.interceptMediaKey(event)
+ }
+
+ private fun dispatchMenuKeyEvent(): Boolean {
+ val shouldUnlockOnMenuPressed =
+ isDeviceInteractive() &&
+ (statusBarStateController.state != StatusBarState.SHADE) &&
+ statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
+ if (shouldUnlockOnMenuPressed) {
+ return collapseShadeLockedOrShowPrimaryBouncer()
+ }
+ return false
+ }
+
+ private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean {
+ when (statusBarStateController.state) {
+ StatusBarState.SHADE -> return false
+ StatusBarState.SHADE_LOCKED -> {
+ shadeController.animateCollapseShadeForced()
+ return true
+ }
+ StatusBarState.KEYGUARD -> {
+ statusBarKeyguardViewManager.showPrimaryBouncer(true)
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun dispatchVolumeKeyEvent(event: KeyEvent): Boolean {
+ mediaSessionLegacyHelperWrapper
+ .getHelper(context)
+ .sendVolumeKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE, true)
+ return true
+ }
+
+ private fun isDeviceInteractive(): Boolean {
+ return keyguardInteractor.wakefulnessModel.value.isDeviceInteractive()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt
new file mode 100644
index 0000000..9924369
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSessionLegacyHelper
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Injectable wrapper around `MediaSessionLegacyHelper` functions */
+@SysUISingleton
+class MediaSessionLegacyHelperWrapper @Inject constructor() {
+ fun getHelper(context: Context): MediaSessionLegacyHelper {
+ return MediaSessionLegacyHelper.getHelper(context)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index a3d1d8c..d8824983 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -515,11 +515,13 @@
mSeekBar.setOnTouchListener((v, event) -> false);
updateIconAreaClickListener((v) -> {
if (device.getCurrentVolume() == 0) {
+ mController.logInteractionUnmuteDevice(device);
mSeekBar.setVolume(UNMUTE_DEFAULT_VOLUME);
mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME);
updateUnmutedVolumeIcon();
mIconAreaLayout.setOnTouchListener(((iconV, event) -> false));
} else {
+ mController.logInteractionMuteDevice(device);
mSeekBar.resetVolume();
mController.adjustVolume(device, 0);
updateMutedVolumeIcon();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b431bab..13cd8e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -843,6 +843,14 @@
mMetricLogger.logInteractionAdjustVolume(device);
}
+ void logInteractionMuteDevice(MediaDevice device) {
+ mMetricLogger.logInteractionMute(device);
+ }
+
+ void logInteractionUnmuteDevice(MediaDevice device) {
+ mMetricLogger.logInteractionUnmute(device);
+ }
+
String getPackageName() {
return mPackageName;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 412d1a3..ffd626a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -154,6 +154,38 @@
}
/**
+ * Do the metric logging of muting device.
+ */
+ public void logInteractionMute(MediaDevice source) {
+ if (DEBUG) {
+ Log.d(TAG, "logInteraction - Mute");
+ }
+
+ SysUiStatsLog.write(
+ SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
+ SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__MUTE,
+ getInteractionDeviceType(source),
+ getLoggingPackageName(),
+ source.isSuggestedDevice());
+ }
+
+ /**
+ * Do the metric logging of unmuting device.
+ */
+ public void logInteractionUnmute(MediaDevice source) {
+ if (DEBUG) {
+ Log.d(TAG, "logInteraction - Unmute");
+ }
+
+ SysUiStatsLog.write(
+ SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
+ SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__UNMUTE,
+ getInteractionDeviceType(source),
+ getLoggingPackageName(),
+ source.isSuggestedDevice());
+ }
+
+ /**
* Do the metric logging of content switching failure.
*
* @param deviceItemList media item list for device count updating
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e134f7c..ae0ab84 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,6 +25,7 @@
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -1714,10 +1715,12 @@
private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
final InsetsFrameProvider navBarProvider =
- new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars())
- .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
- new InsetsFrameProvider.InsetsSizeOverride(
- TYPE_INPUT_METHOD, null)});
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars());
+ if (!ENABLE_HIDE_IME_CAPTION_BAR) {
+ navBarProvider.setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
+ new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+ });
+ }
if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) {
navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight));
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index e8683fb..fb99775 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -63,6 +63,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setDialogTitle(R.string.screenrecord_permission_dialog_title)
+ setTitle(R.string.screenrecord_title)
setStartButtonText(R.string.screenrecord_permission_dialog_continue)
setStartButtonOnClickListener { v: View? ->
onStartRecordingClicked?.run()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 132cd61..014093d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -985,6 +985,7 @@
// Make sure the clock is in the correct position after the unlock animation
// so that it's not in the wrong place when we show the keyguard again.
positionClockAndNotifications(true /* forceClockUpdate */);
+ mScrimController.onUnlockAnimationFinished();
}
private void unlockAnimationStarted(
@@ -1243,6 +1244,13 @@
mKeyguardStatusViewController.init();
}
mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ int oldHeight = oldBottom - oldTop;
+ if (v.getHeight() != oldHeight) {
+ mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
+ }
+ });
updateClockAppearance();
@@ -3198,6 +3206,11 @@
}
}
+ @Override
+ public void performHapticFeedback(int constant) {
+ mVibratorHelper.performHapticFeedback(mView, constant);
+ }
+
private class ShadeHeadsUpTrackerImpl implements ShadeHeadsUpTracker {
@Override
public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 832a25b..798f2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -21,8 +21,6 @@
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.StatusBarManager;
-import android.media.AudioManager;
-import android.media.session.MediaSessionLegacyHelper;
import android.os.PowerManager;
import android.util.Log;
import android.view.GestureDetector;
@@ -47,6 +45,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -101,13 +100,21 @@
private final NotificationInsetsController mNotificationInsetsController;
private final boolean mIsTrackpadCommonEnabled;
private final FeatureFlags mFeatureFlags;
+ private final KeyEventInteractor mKeyEventInteractor;
private GestureDetector mPulsingWakeupGestureHandler;
private GestureDetector mDreamingWakeupGestureHandler;
private View mBrightnessMirror;
private boolean mTouchActive;
private boolean mTouchCancelled;
private MotionEvent mDownEvent;
+ // TODO rename to mLaunchAnimationRunning
private boolean mExpandAnimationRunning;
+ /**
+ * When mExpandAnimationRunning is true and the touch dispatcher receives a down even after
+ * uptime exceeds this, the dispatcher will stop blocking touches for the launch animation,
+ * which has presumabely not completed due to an error.
+ */
+ private long mLaunchAnimationTimeout;
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarViewController mStatusBarViewController;
private final CentralSurfaces mService;
@@ -164,7 +171,8 @@
FeatureFlags featureFlags,
SystemClock clock,
BouncerMessageInteractor bouncerMessageInteractor,
- BouncerLogger bouncerLogger) {
+ BouncerLogger bouncerLogger,
+ KeyEventInteractor keyEventInteractor) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -190,6 +198,7 @@
mNotificationInsetsController = notificationInsetsController;
mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
mFeatureFlags = featureFlags;
+ mKeyEventInteractor = keyEventInteractor;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -278,7 +287,12 @@
return logDownDispatch(ev, "touch cancelled", false);
}
if (mExpandAnimationRunning) {
- return logDownDispatch(ev, "expand animation running", false);
+ if (isDown && mClock.uptimeMillis() > mLaunchAnimationTimeout) {
+ mShadeLogger.d("NSWVC: launch animation timed out");
+ setExpandAnimationRunning(false);
+ } else {
+ return logDownDispatch(ev, "expand animation running", false);
+ }
}
if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -457,44 +471,17 @@
@Override
public boolean interceptMediaKey(KeyEvent event) {
- return mService.interceptMediaKey(event);
+ return mKeyEventInteractor.interceptMediaKey(event);
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
- return mService.dispatchKeyEventPreIme(event);
+ return mKeyEventInteractor.dispatchKeyEventPreIme(event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_BACK:
- if (!down) {
- mBackActionInteractor.onBackRequested();
- }
- return true;
- case KeyEvent.KEYCODE_MENU:
- if (!down) {
- return mService.onMenuPressed();
- }
- break;
- case KeyEvent.KEYCODE_SPACE:
- if (!down) {
- return mService.onSpacePressed();
- }
- break;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- case KeyEvent.KEYCODE_VOLUME_UP:
- if (mStatusBarStateController.isDozing()) {
- MediaSessionLegacyHelper.getHelper(mView.getContext())
- .sendVolumeKeyEvent(
- event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
- return true;
- }
- break;
- }
- return false;
+ return mKeyEventInteractor.dispatchKeyEvent(event);
}
});
@@ -555,8 +542,12 @@
pw.println(mTouchActive);
}
- private void setExpandAnimationRunning(boolean running) {
+ @VisibleForTesting
+ void setExpandAnimationRunning(boolean running) {
if (mExpandAnimationRunning != running) {
+ if (running) {
+ mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000;
+ }
mExpandAnimationRunning = running;
mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index bea12de..6564118 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -263,9 +263,11 @@
resources.getDimensionPixelSize(
R.dimen.shade_header_system_icons_padding_start
),
- systemIcons.paddingTop,
+ resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_top),
resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
- systemIcons.paddingBottom
+ resources.getDimensionPixelSize(
+ R.dimen.shade_header_system_icons_padding_bottom
+ )
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index d5b5c87..182a676 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -248,6 +248,16 @@
/** Starts tracking a shade expansion gesture that originated from the status bar. */
fun startTrackingExpansionFromStatusBar()
+ /**
+ * Performs haptic feedback from a view with a haptic feedback constant.
+ *
+ * The implementation of this method should use the [android.view.View.performHapticFeedback]
+ * method with the provided constant.
+ *
+ * @param[constant] One of [android.view.HapticFeedbackConstants]
+ */
+ fun performHapticFeedback(constant: Int)
+
// ******* End Keyguard Section *********
/** Returns the ShadeHeadsUpTracker. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 287ac52..09b74b2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -86,6 +86,8 @@
return false
}
override fun startTrackingExpansionFromStatusBar() {}
+ override fun performHapticFeedback(constant: Int) {}
+
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index d3c19b7..5042f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -31,56 +31,68 @@
) {
fun logInitialClick(
entry: NotificationEntry?,
+ index: Integer?,
pendingIntent: PendingIntent
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry?.key
str2 = entry?.ranking?.channel?.id
- str3 = pendingIntent.intent.toString()
+ str3 = pendingIntent.toString()
+ int1 = index?.toInt() ?: Int.MIN_VALUE
}, {
- "ACTION CLICK $str1 (channel=$str2) for pending intent $str3"
+ "ACTION CLICK $str1 (channel=$str2) for pending intent $str3 at index $int1"
})
}
fun logRemoteInputWasHandled(
- entry: NotificationEntry?
+ entry: NotificationEntry?,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry?.key
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Triggered remote input (for $str1))"
+ " [Action click] Triggered remote input (for $str1) at index $int1"
})
}
fun logStartingIntentWithDefaultHandler(
entry: NotificationEntry?,
- pendingIntent: PendingIntent
+ pendingIntent: PendingIntent,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry?.key
- str2 = pendingIntent.intent.toString()
+ str2 = pendingIntent.toString()
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Launching intent $str2 via default handler (for $str1)"
+ " [Action click] Launching intent $str2 via default handler (for $str1 at index $int1)"
})
}
fun logWaitingToCloseKeyguard(
- pendingIntent: PendingIntent
+ pendingIntent: PendingIntent,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = pendingIntent.intent.toString()
+ str1 = pendingIntent.toString()
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Intent $str1 launches an activity, dismissing keyguard first..."
+ " [Action click] Intent $str1 at index $int1 launches an activity, dismissing " +
+ "keyguard first..."
})
}
fun logKeyguardGone(
- pendingIntent: PendingIntent
+ pendingIntent: PendingIntent,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = pendingIntent.intent.toString()
+ str1 = pendingIntent.toString()
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Keyguard dismissed, calling default handler for intent $str1"
+ " [Action click] Keyguard dismissed, calling default handler for intent $str1 at " +
+ "index $int1"
})
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
index abf81c5..692a997 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
@@ -2,10 +2,12 @@
import android.app.Notification
import android.os.RemoteException
+import android.util.Log
import com.android.internal.statusbar.IStatusBarService
import com.android.internal.statusbar.NotificationVisibility
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.util.Assert
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -21,7 +23,8 @@
@SysUISingleton
public class NotificationClickNotifier @Inject constructor(
val barService: IStatusBarService,
- @Main val mainExecutor: Executor
+ @Main val mainExecutor: Executor,
+ @UiBackground val backgroundExecutor: Executor
) {
val listeners = mutableListOf<NotificationInteractionListener>()
@@ -48,13 +51,14 @@
visibility: NotificationVisibility,
generatedByAssistant: Boolean
) {
- try {
- barService.onNotificationActionClick(
- key, actionIndex, action, visibility, generatedByAssistant)
- } catch (e: RemoteException) {
- // nothing
+ backgroundExecutor.execute {
+ try {
+ barService.onNotificationActionClick(
+ key, actionIndex, action, visibility, generatedByAssistant)
+ } catch (e: RemoteException) {
+ // nothing
+ }
}
-
mainExecutor.execute {
notifyListenersAboutInteraction(key)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index da84afe..8089fd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -119,11 +119,14 @@
mPowerInteractor.wakeUpIfDozing(
"NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
+ Integer actionIndex = (Integer)
+ view.getTag(com.android.internal.R.id.notification_action_index_tag);
+
final NotificationEntry entry = getNotificationForParent(view.getParent());
- mLogger.logInitialClick(entry, pendingIntent);
+ mLogger.logInitialClick(entry, actionIndex, pendingIntent);
if (handleRemoteInput(view, pendingIntent)) {
- mLogger.logRemoteInputWasHandled(entry);
+ mLogger.logRemoteInputWasHandled(entry, actionIndex);
return true;
}
@@ -141,9 +144,9 @@
}
Notification.Action action = getActionFromView(view, entry, pendingIntent);
return mCallback.handleRemoteViewClick(view, pendingIntent,
- action == null ? false : action.isAuthenticationRequired(), () -> {
+ action == null ? false : action.isAuthenticationRequired(), actionIndex, () -> {
Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
- mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent);
+ mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent, actionIndex);
boolean started = RemoteViews.startPendingIntent(view, pendingIntent, options);
if (started) releaseNotificationIfKeptForRemoteInputHistory(entry);
return started;
@@ -692,11 +695,13 @@
* @param view
* @param pendingIntent
* @param appRequestedAuth
+ * @param actionIndex
* @param defaultHandler
* @return true iff the click was handled
*/
boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
- boolean appRequestedAuth, ClickHandler defaultHandler);
+ boolean appRequestedAuth, @Nullable Integer actionIndex,
+ ClickHandler defaultHandler);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index d6a14604..6dd24ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -48,8 +48,10 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -420,6 +422,25 @@
}
}
+ /** Returns the id of the currently rendering clock */
+ public String getClockId() {
+ if (mView == null) {
+ return KeyguardClockSwitch.MISSING_CLOCK_ID;
+ }
+
+ View clockSwitch = mView.findViewById(R.id.keyguard_clock_container);
+ if (clockSwitch == null) {
+ Log.e(TAG, "Clock container was missing");
+ return KeyguardClockSwitch.MISSING_CLOCK_ID;
+ }
+ if (!(clockSwitch instanceof KeyguardClockSwitch)) {
+ Log.e(TAG, "Clock container was incorrect type: " + clockSwitch);
+ return KeyguardClockSwitch.MISSING_CLOCK_ID;
+ }
+
+ return ((KeyguardClockSwitch) clockSwitch).getClockId();
+ }
+
private void beginInteractionJankMonitor() {
final boolean shouldPost =
(mIsDozing && mDozeAmount == 0) || (!mIsDozing && mDozeAmount == 1);
@@ -429,6 +450,7 @@
Choreographer.CALLBACK_ANIMATION, this::beginInteractionJankMonitor, null);
} else {
Configuration.Builder builder = Configuration.Builder.withView(getCujType(), mView)
+ .setTag(getClockId())
.setDeferMonitorForAnimationStart(false);
mInteractionJankMonitor.begin(builder);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
new file mode 100644
index 0000000..26dfe3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -0,0 +1,684 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.icon.ui.viewbinder
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.Bundle
+import android.os.Trace
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.ColorInt
+import androidx.annotation.VisibleForTesting
+import androidx.collection.ArrayMap
+import com.android.app.animation.Interpolators
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.internal.util.ContrastColorUtil
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.ViewRefactorFlag
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.CrossFadeHelper
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import java.util.function.Function
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+/**
+ * Controller class for [NotificationIconContainer]. This implementation serves as a temporary
+ * wrapper around [NotificationIconContainerViewBinder], so that external code can continue to
+ * depend on the [NotificationIconAreaController] interface. Once
+ * [LegacyNotificationIconAreaControllerImpl] is removed, this class can go away and the ViewBinder
+ * can be used directly.
+ */
+@SysUISingleton
+class NotificationIconAreaControllerViewBinderWrapperImpl
+@Inject
+constructor(
+ private val context: Context,
+ private val statusBarStateController: StatusBarStateController,
+ private val wakeUpCoordinator: NotificationWakeUpCoordinator,
+ private val bypassController: KeyguardBypassController,
+ private val mediaManager: NotificationMediaManager,
+ notificationListener: NotificationListener,
+ private val dozeParameters: DozeParameters,
+ private val sectionStyleProvider: SectionStyleProvider,
+ private val bubblesOptional: Optional<Bubbles>,
+ demoModeController: DemoModeController,
+ darkIconDispatcher: DarkIconDispatcher,
+ featureFlags: FeatureFlags,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
+ private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel,
+ private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+) :
+ NotificationIconAreaController,
+ DarkIconDispatcher.DarkReceiver,
+ StatusBarStateController.StateListener,
+ NotificationWakeUpCoordinator.WakeUpListener,
+ DemoMode {
+
+ private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
+ private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
+ private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
+ private val tintAreas = ArrayList<Rect>()
+
+ private var iconSize = 0
+ private var iconHPadding = 0
+ private var iconTint = Color.WHITE
+ private var notificationEntries = listOf<ListEntry>()
+ private var notificationIconArea: View? = null
+ private var notificationIcons: NotificationIconContainer? = null
+ private var shelfIcons: NotificationIconContainer? = null
+ private var aodIcons: NotificationIconContainer? = null
+ private var aodBindJob: DisposableHandle? = null
+ private var aodIconAppearTranslation = 0
+ private var animationsEnabled = false
+ private var aodIconTint = 0
+ private var aodIconsVisible = false
+ private var showLowPriority = true
+
+ @VisibleForTesting
+ val settingsListener: NotificationListener.NotificationSettingsListener =
+ object : NotificationListener.NotificationSettingsListener {
+ override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) {
+ showLowPriority = !hideSilentStatusIcons
+ updateStatusBarIcons()
+ }
+ }
+
+ init {
+ statusBarStateController.addCallback(this)
+ wakeUpCoordinator.addListener(this)
+ demoModeController.addCallback(this)
+ notificationListener.addNotificationSettingsListener(settingsListener)
+ initializeNotificationAreaViews(context)
+ reloadAodColor()
+ darkIconDispatcher.addDarkReceiver(this)
+ }
+
+ @VisibleForTesting
+ fun shouldShowLowPriorityIcons(): Boolean {
+ return showLowPriority
+ }
+
+ /** Called by the Keyguard*ViewController whose view contains the aod icons. */
+ override fun setupAodIcons(aodIcons: NotificationIconContainer) {
+ val changed = this.aodIcons != null && aodIcons !== this.aodIcons
+ if (changed) {
+ this.aodIcons!!.setAnimationsEnabled(false)
+ this.aodIcons!!.removeAllViews()
+ aodBindJob?.dispose()
+ }
+ this.aodIcons = aodIcons
+ this.aodIcons!!.setOnLockScreen(true)
+ aodBindJob = NotificationIconContainerViewBinder.bind(aodIcons, aodIconsViewModel)
+ updateAodIconsVisibility(animate = false, forceUpdate = changed)
+ updateAnimations()
+ if (changed) {
+ updateAodNotificationIcons()
+ }
+ updateIconLayoutParams(context)
+ }
+
+ override fun setupShelf(notificationShelfController: NotificationShelfController) =
+ NotificationShelfViewBinderWrapperControllerImpl.unsupported
+
+ override fun setShelfIcons(icons: NotificationIconContainer) {
+ if (shelfRefactor.expectEnabled()) {
+ NotificationIconContainerViewBinder.bind(icons, shelfIconsViewModel)
+ shelfIcons = icons
+ }
+ }
+
+ override fun onDensityOrFontScaleChanged(context: Context) {
+ updateIconLayoutParams(context)
+ }
+
+ /** Returns the view that represents the notification area. */
+ override fun getNotificationInnerAreaView(): View? {
+ return notificationIconArea
+ }
+
+ /**
+ * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the
+ * color that should be used to tint any icons in the notification area.
+ *
+ * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
+ * @param darkIntensity
+ */
+ override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) {
+ this.tintAreas.clear()
+ this.tintAreas.addAll(tintAreas)
+ if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) {
+ this.iconTint = iconTint
+ }
+ applyNotificationIconsTint()
+ }
+
+ /** Updates the notifications with the given list of notifications to display. */
+ override fun updateNotificationIcons(entries: List<ListEntry>) {
+ notificationEntries = entries
+ updateNotificationIcons()
+ }
+
+ private fun updateStatusBarIcons() {
+ updateIconsForLayout(
+ { entry: NotificationEntry -> entry.icons.statusBarIcon },
+ notificationIcons,
+ showAmbient = false /* showAmbient */,
+ showLowPriority = showLowPriority,
+ hideDismissed = true /* hideDismissed */,
+ hideRepliedMessages = true /* hideRepliedMessages */,
+ hideCurrentMedia = false /* hideCurrentMedia */,
+ hidePulsing = false /* hidePulsing */
+ )
+ }
+
+ override fun updateAodNotificationIcons() {
+ if (aodIcons == null) {
+ return
+ }
+ updateIconsForLayout(
+ { entry: NotificationEntry -> entry.icons.aodIcon },
+ aodIcons,
+ showAmbient = false /* showAmbient */,
+ showLowPriority = true /* showLowPriority */,
+ hideDismissed = true /* hideDismissed */,
+ hideRepliedMessages = true /* hideRepliedMessages */,
+ hideCurrentMedia = true /* hideCurrentMedia */,
+ hidePulsing = bypassController.bypassEnabled /* hidePulsing */
+ )
+ }
+
+ override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) {
+ notificationIcons!!.showIconIsolated(icon, animated)
+ }
+
+ override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) {
+ notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
+ }
+
+ override fun onDozingChanged(isDozing: Boolean) {
+ if (aodIcons == null) {
+ return
+ }
+ val animate = (dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking)
+ aodIcons!!.setDozing(isDozing, animate, 0)
+ }
+
+ override fun setAnimationsEnabled(enabled: Boolean) {
+ animationsEnabled = enabled
+ updateAnimations()
+ }
+
+ override fun onStateChanged(newState: Int) {
+ updateAodIconsVisibility(animate = false, forceUpdate = false)
+ updateAnimations()
+ }
+
+ override fun onThemeChanged() {
+ reloadAodColor()
+ updateAodIconColors()
+ }
+
+ override fun getHeight(): Int {
+ return if (aodIcons == null) 0 else aodIcons!!.height
+ }
+
+ @VisibleForTesting
+ fun appearAodIcons() {
+ if (aodIcons == null) {
+ return
+ }
+ if (screenOffAnimationController.shouldAnimateAodIcons()) {
+ aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
+ aodIcons!!.alpha = 0f
+ animateInAodIconTranslation()
+ aodIcons!!
+ .animate()
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(AOD_ICONS_APPEAR_DURATION)
+ .start()
+ } else {
+ aodIcons!!.alpha = 1.0f
+ aodIcons!!.translationY = 0f
+ }
+ }
+
+ override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
+ var animate = true
+ if (!bypassController.bypassEnabled) {
+ animate = dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
+ // We only want the appear animations to happen when the notifications get fully hidden,
+ // since otherwise the unhide animation overlaps
+ animate = animate and isFullyHidden
+ }
+ updateAodIconsVisibility(animate, false /* force */)
+ updateAodNotificationIcons()
+ updateAodIconColors()
+ }
+
+ override fun onPulseExpansionChanged(expandingChanged: Boolean) {
+ if (expandingChanged) {
+ updateAodIconsVisibility(animate = true, forceUpdate = false)
+ }
+ }
+
+ override fun demoCommands(): List<String> {
+ val commands = ArrayList<String>()
+ commands.add(DemoMode.COMMAND_NOTIFICATIONS)
+ return commands
+ }
+
+ override fun dispatchDemoCommand(command: String, args: Bundle) {
+ if (notificationIconArea != null) {
+ val visible = args.getString("visible")
+ val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE
+ notificationIconArea?.visibility = vis
+ }
+ }
+
+ override fun onDemoModeFinished() {
+ if (notificationIconArea != null) {
+ notificationIconArea?.visibility = View.VISIBLE
+ }
+ }
+
+ private fun inflateIconArea(inflater: LayoutInflater): View {
+ return inflater.inflate(R.layout.notification_icon_area, null)
+ }
+
+ /** Initializes the views that will represent the notification area. */
+ private fun initializeNotificationAreaViews(context: Context) {
+ reloadDimens(context)
+ val layoutInflater = LayoutInflater.from(context)
+ notificationIconArea = inflateIconArea(layoutInflater)
+ notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons)
+ NotificationIconContainerViewBinder.bind(notificationIcons!!, statusBarIconsViewModel)
+ }
+
+ private fun updateIconLayoutParams(context: Context) {
+ reloadDimens(context)
+ val params = generateIconLayoutParams()
+ for (i in 0 until notificationIcons!!.childCount) {
+ val child = notificationIcons!!.getChildAt(i)
+ child.layoutParams = params
+ }
+ if (shelfIcons != null) {
+ for (i in 0 until shelfIcons!!.childCount) {
+ val child = shelfIcons!!.getChildAt(i)
+ child.layoutParams = params
+ }
+ }
+ if (aodIcons != null) {
+ for (i in 0 until aodIcons!!.childCount) {
+ val child = aodIcons!!.getChildAt(i)
+ child.layoutParams = params
+ }
+ }
+ }
+
+ private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
+ return FrameLayout.LayoutParams(
+ iconSize + 2 * iconHPadding,
+ statusBarWindowController.statusBarHeight
+ )
+ }
+
+ private fun reloadDimens(context: Context) {
+ val res = context.resources
+ iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp)
+ iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
+ aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation)
+ }
+
+ private fun shouldShowNotificationIcon(
+ entry: NotificationEntry,
+ showAmbient: Boolean,
+ showLowPriority: Boolean,
+ hideDismissed: Boolean,
+ hideRepliedMessages: Boolean,
+ hideCurrentMedia: Boolean,
+ hidePulsing: Boolean
+ ): Boolean {
+ if (!showAmbient && sectionStyleProvider.isMinimized(entry)) {
+ return false
+ }
+ if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) {
+ return false
+ }
+ if (!showLowPriority && sectionStyleProvider.isSilent(entry)) {
+ return false
+ }
+ if (entry.isRowDismissed && hideDismissed) {
+ return false
+ }
+ if (hideRepliedMessages && entry.isLastMessageFromReply) {
+ return false
+ }
+ // showAmbient == show in shade but not shelf
+ if (!showAmbient && entry.shouldSuppressStatusBar()) {
+ return false
+ }
+ if (
+ hidePulsing &&
+ entry.showingPulsing() &&
+ (!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed)
+ ) {
+ return false
+ }
+ return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) {
+ false
+ } else true
+ }
+
+ private fun updateNotificationIcons() {
+ Trace.beginSection("NotificationIconAreaController.updateNotificationIcons")
+ updateStatusBarIcons()
+ updateShelfIcons()
+ updateAodNotificationIcons()
+ applyNotificationIconsTint()
+ Trace.endSection()
+ }
+
+ private fun updateShelfIcons() {
+ if (shelfIcons == null) {
+ return
+ }
+ updateIconsForLayout(
+ { entry: NotificationEntry -> entry.icons.shelfIcon },
+ shelfIcons,
+ showAmbient = true,
+ showLowPriority = true,
+ hideDismissed = false,
+ hideRepliedMessages = false,
+ hideCurrentMedia = false,
+ hidePulsing = false
+ )
+ }
+
+ /**
+ * Updates the notification icons for a host layout. This will ensure that the notification host
+ * layout will have the same icons like the ones in here.
+ *
+ * @param function A function to look up an icon view based on an entry
+ * @param hostLayout which layout should be updated
+ * @param showAmbient should ambient notification icons be shown
+ * @param showLowPriority should icons from silent notifications be shown
+ * @param hideDismissed should dismissed icons be hidden
+ * @param hideRepliedMessages should messages that have been replied to be hidden
+ * @param hidePulsing should pulsing notifications be hidden
+ */
+ private fun updateIconsForLayout(
+ function: Function<NotificationEntry, StatusBarIconView?>,
+ hostLayout: NotificationIconContainer?,
+ showAmbient: Boolean,
+ showLowPriority: Boolean,
+ hideDismissed: Boolean,
+ hideRepliedMessages: Boolean,
+ hideCurrentMedia: Boolean,
+ hidePulsing: Boolean,
+ ) {
+ val toShow = ArrayList<StatusBarIconView>(notificationEntries.size)
+ // Filter out ambient notifications and notification children.
+ for (i in notificationEntries.indices) {
+ val entry = notificationEntries[i].representativeEntry
+ if (entry != null && entry.row != null) {
+ if (
+ shouldShowNotificationIcon(
+ entry,
+ showAmbient,
+ showLowPriority,
+ hideDismissed,
+ hideRepliedMessages,
+ hideCurrentMedia,
+ hidePulsing
+ )
+ ) {
+ val iconView = function.apply(entry)
+ if (iconView != null) {
+ toShow.add(iconView)
+ }
+ }
+ }
+ }
+
+ // In case we are changing the suppression of a group, the replacement shouldn't flicker
+ // and it should just be replaced instead. We therefore look for notifications that were
+ // just replaced by the child or vice-versa to suppress this.
+ val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>()
+ val toRemove = ArrayList<View>()
+ for (i in 0 until hostLayout!!.childCount) {
+ val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue
+ if (!toShow.contains(child)) {
+ var iconWasReplaced = false
+ val removedGroupKey = child.notification.groupKey
+ for (j in toShow.indices) {
+ val candidate = toShow[j]
+ if (
+ candidate.sourceIcon.sameAs(child.sourceIcon) &&
+ candidate.notification.groupKey == removedGroupKey
+ ) {
+ if (!iconWasReplaced) {
+ iconWasReplaced = true
+ } else {
+ iconWasReplaced = false
+ break
+ }
+ }
+ }
+ if (iconWasReplaced) {
+ var statusBarIcons = replacingIcons[removedGroupKey]
+ if (statusBarIcons == null) {
+ statusBarIcons = ArrayList()
+ replacingIcons[removedGroupKey] = statusBarIcons
+ }
+ statusBarIcons.add(child.statusBarIcon)
+ }
+ toRemove.add(child)
+ }
+ }
+ // removing all duplicates
+ val duplicates = ArrayList<String?>()
+ for (key in replacingIcons.keys) {
+ val statusBarIcons = replacingIcons[key]!!
+ if (statusBarIcons.size != 1) {
+ duplicates.add(key)
+ }
+ }
+ replacingIcons.removeAll(duplicates)
+ hostLayout.setReplacingIcons(replacingIcons)
+ val toRemoveCount = toRemove.size
+ for (i in 0 until toRemoveCount) {
+ hostLayout.removeView(toRemove[i])
+ }
+ val params = generateIconLayoutParams()
+ for (i in toShow.indices) {
+ val v = toShow[i]
+ // The view might still be transiently added if it was just removed and added again
+ hostLayout.removeTransientView(v)
+ if (v.parent == null) {
+ if (hideDismissed) {
+ v.setOnDismissListener(updateStatusBarIcons)
+ }
+ hostLayout.addView(v, i, params)
+ }
+ }
+ hostLayout.setChangingViewPositions(true)
+ // Re-sort notification icons
+ val childCount = hostLayout.childCount
+ for (i in 0 until childCount) {
+ val actual = hostLayout.getChildAt(i)
+ val expected = toShow[i]
+ if (actual === expected) {
+ continue
+ }
+ hostLayout.removeView(expected)
+ hostLayout.addView(expected, i)
+ }
+ hostLayout.setChangingViewPositions(false)
+ hostLayout.setReplacingIcons(null)
+ }
+
+ /** Applies [.mIconTint] to the notification icons. */
+ private fun applyNotificationIconsTint() {
+ for (i in 0 until notificationIcons!!.childCount) {
+ val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView
+ if (iv.width != 0) {
+ updateTintForIcon(iv, iconTint)
+ } else {
+ iv.executeOnLayout { updateTintForIcon(iv, iconTint) }
+ }
+ }
+ updateAodIconColors()
+ }
+
+ private fun updateTintForIcon(v: StatusBarIconView, tint: Int) {
+ val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
+ var color = StatusBarIconView.NO_COLOR
+ val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
+ if (colorize) {
+ color = DarkIconDispatcher.getTint(tintAreas, v, tint)
+ }
+ v.staticDrawableColor = color
+ v.setDecorColor(tint)
+ }
+
+ private fun updateAnimations() {
+ val inShade = statusBarStateController.state == StatusBarState.SHADE
+ if (aodIcons != null) {
+ aodIcons!!.setAnimationsEnabled(animationsEnabled && !inShade)
+ }
+ notificationIcons!!.setAnimationsEnabled(animationsEnabled && inShade)
+ }
+
+ private fun animateInAodIconTranslation() {
+ aodIcons!!
+ .animate()
+ .setInterpolator(Interpolators.DECELERATE_QUINT)
+ .translationY(0f)
+ .setDuration(AOD_ICONS_APPEAR_DURATION)
+ .start()
+ }
+
+ private fun reloadAodColor() {
+ aodIconTint =
+ Utils.getColorAttrDefaultColor(
+ context,
+ R.attr.wallpaperTextColor,
+ DEFAULT_AOD_ICON_COLOR
+ )
+ }
+
+ private fun updateAodIconColors() {
+ if (aodIcons != null) {
+ for (i in 0 until aodIcons!!.childCount) {
+ val iv = aodIcons!!.getChildAt(i) as StatusBarIconView
+ if (iv.width != 0) {
+ updateTintForIcon(iv, aodIconTint)
+ } else {
+ iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) }
+ }
+ }
+ }
+ }
+
+ private fun updateAodIconsVisibility(animate: Boolean, forceUpdate: Boolean) {
+ if (aodIcons == null) {
+ return
+ }
+ var visible = (bypassController.bypassEnabled || wakeUpCoordinator.notificationsFullyHidden)
+
+ // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
+ // playing, in which case we want them to be visible since we're animating in the AOD UI and
+ // will be switching to KEYGUARD shortly.
+ if (
+ statusBarStateController.state != StatusBarState.KEYGUARD &&
+ !screenOffAnimationController.shouldShowAodIconsWhenShade()
+ ) {
+ visible = false
+ }
+ if (visible && wakeUpCoordinator.isPulseExpanding() && !bypassController.bypassEnabled) {
+ visible = false
+ }
+ if (aodIconsVisible != visible || forceUpdate) {
+ aodIconsVisible = visible
+ aodIcons!!.animate().cancel()
+ if (animate) {
+ val wasFullyInvisible = aodIcons!!.visibility != View.VISIBLE
+ if (aodIconsVisible) {
+ if (wasFullyInvisible) {
+ // No fading here, let's just appear the icons instead!
+ aodIcons!!.visibility = View.VISIBLE
+ aodIcons!!.alpha = 1.0f
+ appearAodIcons()
+ } else {
+ // Let's make sure the icon are translated to 0, since we cancelled it above
+ animateInAodIconTranslation()
+ // We were fading out, let's fade in instead
+ CrossFadeHelper.fadeIn(aodIcons)
+ }
+ } else {
+ // Let's make sure the icon are translated to 0, since we cancelled it above
+ animateInAodIconTranslation()
+ CrossFadeHelper.fadeOut(aodIcons)
+ }
+ } else {
+ aodIcons!!.alpha = 1.0f
+ aodIcons!!.translationY = 0f
+ aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE
+ }
+ }
+ }
+
+ companion object {
+ private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+
+ @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
new file mode 100644
index 0000000..8293bb3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import kotlinx.coroutines.DisposableHandle
+
+/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
+object NotificationIconContainerViewBinder {
+ fun bind(
+ view: NotificationIconContainer,
+ viewModel: NotificationIconContainerViewModel,
+ ): DisposableHandle {
+ return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) {} }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
new file mode 100644
index 0000000..f68b0ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.icon.ui.viewmodel
+
+import javax.inject.Inject
+
+/** View-model for the row of notification icons displayed on the always-on display. */
+class NotificationIconContainerAlwaysOnDisplayViewModel @Inject constructor() :
+ NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
new file mode 100644
index 0000000..933c76f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.icon.ui.viewmodel
+
+import javax.inject.Inject
+
+/** View-model for the overflow row of notification icons displayed in the notification shade. */
+class NotificationIconContainerShelfViewModel @Inject constructor() :
+ NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
new file mode 100644
index 0000000..2217646
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.icon.ui.viewmodel
+
+import javax.inject.Inject
+
+/** View-model for the row of notification icons displayed in the status bar, */
+class NotificationIconContainerStatusBarViewModel @Inject constructor() :
+ NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
new file mode 100644
index 0000000..892b2be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.icon.ui.viewmodel
+
+/**
+ * View-model for the row of notification icons displayed in the NotificationShelf, StatusBar, and
+ * AOD.
+ */
+interface NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 22a87a7..b92c51f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -64,8 +64,10 @@
override fun setOnClickListener(listener: View.OnClickListener) = unsupported
- private val unsupported: Nothing
- get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled")
+ companion object {
+ val unsupported: Nothing
+ get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled")
+ }
}
/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 4668aa4..6db8df9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -28,7 +28,6 @@
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
-import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Configuration;
@@ -150,6 +149,7 @@
public class NotificationStackScrollLayoutController {
private static final String TAG = "StackScrollerController";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final String HIGH_PRIORITY = "high_priority";
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 5c28be3..af09bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,7 +25,6 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
import android.view.View;
@@ -276,19 +275,11 @@
void userActivity();
- boolean interceptMediaKey(KeyEvent event);
-
- boolean dispatchKeyEventPreIme(KeyEvent event);
-
- boolean onMenuPressed();
-
void endAffordanceLaunch();
/** Should the keyguard be hidden immediately in response to a back press/gesture. */
boolean shouldKeyguardHideImmediately();
- boolean onSpacePressed();
-
void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction,
Runnable cancelAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6431ef9..2bc7b99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -33,12 +34,15 @@
import android.os.Vibrator;
import android.util.Log;
import android.util.Slog;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.LetterboxDetails;
@@ -49,6 +53,7 @@
import com.android.systemui.camera.CameraIntents;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
@@ -107,6 +112,7 @@
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final QuickSettingsController mQsController;
private final QSHost mQSHost;
+ private final FeatureFlags mFeatureFlags;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -144,7 +150,8 @@
Lazy<CameraLauncher> cameraLauncherLazy,
UserTracker userTracker,
QSHost qsHost,
- ActivityStarter activityStarter) {
+ ActivityStarter activityStarter,
+ FeatureFlags featureFlags) {
mCentralSurfaces = centralSurfaces;
mQsController = quickSettingsController;
mContext = context;
@@ -171,6 +178,7 @@
mCameraLauncherLazy = cameraLauncherLazy;
mUserTracker = userTracker;
mQSHost = qsHost;
+ mFeatureFlags = featureFlags;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -314,7 +322,7 @@
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
if (mShadeViewController.isFullyCollapsed()) {
if (mVibrateOnOpening) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ vibrateOnNavigationKeyDown();
}
mShadeViewController.expand(true /* animate */);
mNotificationStackScrollLayoutController.setWillExpand(true);
@@ -587,4 +595,15 @@
}
return VibrationEffect.createWaveform(timings, /* repeat= */ -1);
}
+
+ @VisibleForTesting
+ void vibrateOnNavigationKeyDown() {
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ mShadeViewController.performHapticFeedback(
+ HapticFeedbackConstants.GESTURE_START
+ );
+ } else {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 8ffd43a..ccb87bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -89,7 +89,6 @@
import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
@@ -2636,44 +2635,6 @@
}
@Override
- public boolean interceptMediaKey(KeyEvent event) {
- return mState == StatusBarState.KEYGUARD
- && mStatusBarKeyguardViewManager.interceptMediaKey(event);
- }
-
- /**
- * While IME is active and a BACK event is detected, check with
- * {@link StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event
- * should be handled before routing to IME, in order to prevent the user having to hit back
- * twice to exit bouncer.
- */
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_BACK:
- if (mState == StatusBarState.KEYGUARD
- && mStatusBarKeyguardViewManager.dispatchBackKeyEventPreIme()) {
- return mBackActionInteractor.onBackRequested();
- }
- }
- return false;
- }
-
- protected boolean shouldUnlockOnMenuPressed() {
- return mDeviceInteractive && mState != StatusBarState.SHADE
- && mStatusBarKeyguardViewManager.shouldDismissOnMenuPressed();
- }
-
- @Override
- public boolean onMenuPressed() {
- if (shouldUnlockOnMenuPressed()) {
- mShadeController.animateCollapseShadeForced();
- return true;
- }
- return false;
- }
-
- @Override
public void endAffordanceLaunch() {
releaseGestureWakeLock();
mCameraLauncherLazy.get().setLaunchingAffordance(false);
@@ -2692,15 +2653,6 @@
return (isScrimmedBouncer || isBouncerOverDream);
}
- @Override
- public boolean onSpacePressed() {
- if (mDeviceInteractive && mState != StatusBarState.SHADE) {
- mShadeController.animateCollapseShadeForced();
- return true;
- }
- return false;
- }
-
private void showBouncerOrLockScreenIfKeyguard() {
// If the keyguard is animating away, we aren't really the keyguard anymore and should not
// show the bouncer/lockscreen.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 0bf0f4b..d22ed38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.phone;
import android.content.Context;
@@ -43,6 +58,8 @@
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.wm.shell.bubbles.Bubbles;
+import org.jetbrains.annotations.NotNull;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -55,13 +72,13 @@
* normally reserved for notifications.
*/
@SysUISingleton
-public class NotificationIconAreaController implements
+public class LegacyNotificationIconAreaControllerImpl implements
+ NotificationIconAreaController,
DarkReceiver,
StatusBarStateController.StateListener,
NotificationWakeUpCoordinator.WakeUpListener,
DemoMode {
- public static final String HIGH_PRIORITY = "high_priority";
private static final long AOD_ICONS_APPEAR_DURATION = 200;
@ColorInt
private static final int DEFAULT_AOD_ICON_COLOR = 0xffffffff;
@@ -110,7 +127,7 @@
};
@Inject
- public NotificationIconAreaController(
+ public LegacyNotificationIconAreaControllerImpl(
Context context,
StatusBarStateController statusBarStateController,
NotificationWakeUpCoordinator wakeUpCoordinator,
@@ -192,7 +209,7 @@
}
}
- public void onDensityOrFontScaleChanged(Context context) {
+ public void onDensityOrFontScaleChanged(@NotNull Context context) {
updateIconLayoutParams(context);
}
@@ -493,7 +510,7 @@
mNotificationIcons.showIconIsolated(icon, animated);
}
- public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
+ public void setIsolatedIconLocation(@NotNull Rect iconDrawingRect, boolean requireStateUpdate) {
mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
new file mode 100644
index 0000000..0079f7c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.phone
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.collection.ListEntry
+
+/**
+ * A controller for the space in the status bar to the left of the system icons. This area is
+ * normally reserved for notifications.
+ */
+interface NotificationIconAreaController {
+ /** Called by the Keyguard*ViewController whose view contains the aod icons. */
+ fun setupAodIcons(aodIcons: NotificationIconContainer)
+ fun setupShelf(notificationShelfController: NotificationShelfController)
+ fun setShelfIcons(icons: NotificationIconContainer)
+ fun onDensityOrFontScaleChanged(context: Context)
+
+ /** Returns the view that represents the notification area. */
+ fun getNotificationInnerAreaView(): View?
+
+ /** Updates the notifications with the given list of notifications to display. */
+ fun updateNotificationIcons(entries: List<@JvmSuppressWildcards ListEntry>)
+ fun updateAodNotificationIcons()
+ fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean)
+ fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean)
+ fun setAnimationsEnabled(enabled: Boolean)
+ fun onThemeChanged()
+ fun getHeight(): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
new file mode 100644
index 0000000..d1ddd51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.phone
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl
+import dagger.Module
+import dagger.Provides
+import javax.inject.Provider
+
+@Module
+object NotificationIconAreaControllerModule {
+ @Provides
+ fun provideNotificationIconAreaControllerImpl(
+ featureFlags: FeatureFlags,
+ legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>,
+ newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>,
+ ): NotificationIconAreaController =
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ newProvider.get()
+ } else {
+ legacyProvider.get()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index fc66138..62a8cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -709,6 +709,11 @@
}
}
+ public void onUnlockAnimationFinished() {
+ mAnimatingPanelExpansionOnUnlock = false;
+ applyAndDispatchState();
+ }
+
/**
* Set the amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 7fe01825..a6284e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -32,6 +32,8 @@
import android.view.View;
import android.view.ViewParent;
+import androidx.annotation.Nullable;
+
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -254,16 +256,16 @@
@Override
public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
- boolean appRequestedAuth,
+ boolean appRequestedAuth, @Nullable Integer actionIndex,
NotificationRemoteInputManager.ClickHandler defaultHandler) {
final boolean isActivity = pendingIntent.isActivity();
if (isActivity || appRequestedAuth) {
- mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
+ mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent, actionIndex);
final boolean afterKeyguardGone = mActivityIntentHelper
.wouldPendingLaunchResolverActivity(pendingIntent,
mLockscreenUserManager.getCurrentUserId());
mActivityStarter.dismissKeyguardThenExecute(() -> {
- mActionClickLogger.logKeyguardGone(pendingIntent);
+ mActionClickLogger.logKeyguardGone(pendingIntent, actionIndex);
try {
ActivityManager.getService().resumeAppSwitches();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 1bceb29..cd1afc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -223,10 +223,14 @@
}
.setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
true /* animate */)
- interactionJankMonitor.begin(
- notifShadeWindowControllerLazy.get().windowRootView,
- CUJ_SCREEN_OFF_SHOW_AOD
- )
+ val builder = InteractionJankMonitor.Configuration.Builder
+ .withView(
+ InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD,
+ notifShadeWindowControllerLazy.get().windowRootView
+ )
+ .setTag(statusBarStateControllerImpl.getClockId())
+
+ interactionJankMonitor.begin(builder)
}
override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index b349696..438f0f4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -18,6 +18,8 @@
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static com.android.systemui.flags.Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -37,6 +39,7 @@
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -59,8 +62,13 @@
@Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@Mock
+ private ConnectedDisplayKeyguardPresentation.Factory
+ mConnectedDisplayKeyguardPresentationFactory;
+ @Mock
private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
@Mock
+ private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
+ @Mock
private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
@Mock
private KeyguardStateController mKeyguardStateController;
@@ -69,7 +77,7 @@
private Executor mBackgroundExecutor = Runnable::run;
private KeyguardDisplayManager mManager;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
+ private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
// The default and secondary displays are both in the default group
private Display mDefaultDisplay;
private Display mSecondaryDisplay;
@@ -80,10 +88,14 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, false);
mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
- mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController));
+ mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController,
+ mConnectedDisplayKeyguardPresentationFactory, mFakeFeatureFlags));
doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
+ doReturn(mConnectedDisplayKeyguardPresentation).when(
+ mConnectedDisplayKeyguardPresentationFactory).create(any());
mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -138,4 +150,15 @@
when(mKeyguardStateController.isOccluded()).thenReturn(true);
verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
}
+
+ @Test
+ public void testShow_withClockPresentationFlagEnabled_presentationCreated() {
+ when(mManager.createPresentation(any())).thenCallRealMethod();
+ mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, true);
+ mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
+
+ mManager.show();
+
+ verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index a9aed2f..b5317fa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -620,6 +620,51 @@
configurationListenerArgumentCaptor.value.onUiModeChanged()
verify(view).reloadColors()
}
+ @Test
+ fun onOrientationChanged_landscapeKeyguardFlagDisabled_blockReinflate() {
+ featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
+
+ // Run onOrientationChanged
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onOrientationChanged(
+ Configuration.ORIENTATION_LANDSCAPE
+ )
+ // Verify view is reinflated when flag is on
+ verify(viewFlipperController, never()).clearViews()
+ verify(viewFlipperController, never())
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ }
+
+ @Test
+ fun onOrientationChanged_landscapeKeyguardFlagEnabled_doesReinflate() {
+ featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true)
+
+ // Run onOrientationChanged
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onOrientationChanged(
+ Configuration.ORIENTATION_LANDSCAPE
+ )
+ // Verify view is reinflated when flag is on
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ }
@Test
fun hasDismissActions() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 7d23c80..b8b0198 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -77,7 +77,7 @@
public void updatePosition_primaryClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", false, true));
mController.updatePosition(10, 15, 20f, true);
@@ -92,7 +92,7 @@
public void updatePosition_alternateClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig(true, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", true, true));
mController.updatePosition(10, 15, 20f, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
new file mode 100644
index 0000000..632d149
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyEventInteractorTest : SysuiTestCase() {
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var keyguardInteractorWithDependencies:
+ KeyguardInteractorFactory.WithDependencies
+ @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
+ @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+ private lateinit var underTest: KeyEventInteractor
+
+ @Before
+ fun setup() {
+ keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+ underTest =
+ KeyEventInteractor(
+ backActionInteractor,
+ keyguardKeyEventInteractor,
+ )
+ }
+
+ @Test
+ fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
+ val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
+ val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+
+ // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
+ .thenReturn(false)
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
+ .thenReturn(false)
+
+ // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
+ assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
+ // THEN back event isn't handled on ACTION_DOWN
+ verify(backActionInteractor, never()).onBackRequested()
+
+ // WHEN back key event ACTION_UP
+ assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
+ // THEN back event is handled on ACTION_UP
+ verify(backActionInteractor).onBackRequested()
+ }
+
+ @Test
+ fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
+ assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
+ }
+
+ @Test
+ fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
+ assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
+ }
+
+ @Test
+ fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+ assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+ }
+
+ @Test
+ fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+ assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
+ assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
+ val keyEvent =
+ KeyEvent(
+ KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
+ assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index dcaafe8..6fcf54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
@@ -47,7 +47,7 @@
@SmallTest
@RoboPilotTest
@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() {
private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var underTest: LightRevealScrimRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
new file mode 100644
index 0000000..e0ae0c3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2023 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.systemui.keyguard.domain.interactor
+
+import android.media.AudioManager
+import android.media.session.MediaSessionLegacyHelper
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardKeyEventInteractorTest : SysuiTestCase() {
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ private val actionDownVolumeDownKeyEvent =
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN)
+ private val actionDownVolumeUpKeyEvent =
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+ private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+ private val awakeWakefulnessMode =
+ WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ private val asleepWakefulnessMode =
+ WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+
+ private lateinit var keyguardInteractorWithDependencies:
+ KeyguardInteractorFactory.WithDependencies
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var shadeController: ShadeController
+ @Mock private lateinit var mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper
+ @Mock private lateinit var mediaSessionLegacyHelper: MediaSessionLegacyHelper
+ @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+ private lateinit var underTest: KeyguardKeyEventInteractor
+
+ @Before
+ fun setup() {
+ whenever(mediaSessionLegacyHelperWrapper.getHelper(any()))
+ .thenReturn(mediaSessionLegacyHelper)
+ keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+ underTest =
+ KeyguardKeyEventInteractor(
+ context,
+ statusBarStateController,
+ keyguardInteractorWithDependencies.keyguardInteractor,
+ statusBarKeyguardViewManager,
+ shadeController,
+ mediaSessionLegacyHelperWrapper,
+ backActionInteractor,
+ )
+ }
+
+ @Test
+ fun dispatchKeyEvent_volumeKey_dozing_handlesEvents() {
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isTrue()
+ verify(mediaSessionLegacyHelper)
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeDownKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isTrue()
+ verify(mediaSessionLegacyHelper)
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeUpKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+ }
+
+ @Test
+ fun dispatchKeyEvent_volumeKey_notDozing_doesNotHandleEvents() {
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isFalse()
+ verify(mediaSessionLegacyHelper, never())
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeDownKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+
+ assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isFalse()
+ verify(mediaSessionLegacyHelper, never())
+ .sendVolumeKeyEvent(
+ eq(actionDownVolumeUpKeyEvent),
+ eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+ eq(true)
+ )
+ }
+
+ @Test
+ fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_showsPrimaryBouncer() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU)
+ }
+
+ @Test
+ fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+ whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU)
+ }
+
+ @Test
+ fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_doNothing() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+ verifyActionsDoNothing(KeyEvent.KEYCODE_MENU)
+ }
+
+ @Test
+ fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_showsPrimaryBouncer() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE)
+ }
+
+ @Test
+ fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE)
+ }
+
+ @Test
+ fun dispatchKeyEvent_enterActionUp_interactiveKeyguard_showsPrimaryBouncer() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER)
+ }
+
+ @Test
+ fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER)
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_back_keyguard_onBackRequested() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+
+ whenever(backActionInteractor.onBackRequested()).thenReturn(false)
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+ verify(backActionInteractor).onBackRequested()
+ clearInvocations(backActionInteractor)
+
+ whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isTrue()
+ verify(backActionInteractor).onBackRequested()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_back_keyguard_SBKVMdoesNotHandle_neverOnBackRequested() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(false)
+ whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+ verify(backActionInteractor, never()).onBackRequested()
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_back_shade_neverOnBackRequested() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+ whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+ assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+ verify(backActionInteractor, never()).onBackRequested()
+ }
+
+ @Test
+ fun interceptMediaKey_keyguard_SBKVMdoesNotHandle_doesNotHandleMediaKey() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+
+ assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+ verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+ }
+
+ @Test
+ fun interceptMediaKey_keyguard_handleMediaKey() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+
+ assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+ verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+ }
+
+ @Test
+ fun interceptMediaKey_shade_doesNotHandleMediaKey() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+ assertThat(
+ underTest.interceptMediaKey(
+ KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+ )
+ )
+ .isFalse()
+ verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
+ }
+
+ private fun verifyActionUpCollapsesTheShade(keycode: Int) {
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(shadeController).animateCollapseShadeForced()
+ }
+
+ private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) {
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true))
+ }
+
+ private fun verifyActionsDoNothing(keycode: Int) {
+ // action down: does nothing
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+ // action up: doesNothing
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index eb4ae1a..7aeafeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -539,6 +539,23 @@
}
@Test
+ public void onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL() {
+ ArgumentCaptor<View.OnLayoutChangeListener> captor =
+ ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+ verify(mKeyguardStatusView).addOnLayoutChangeListener(captor.capture());
+ View.OnLayoutChangeListener listener = captor.getValue();
+
+ clearInvocations(mNotificationStackScrollLayoutController);
+
+ when(mKeyguardStatusView.getHeight()).thenReturn(0);
+ listener.onLayoutChange(mKeyguardStatusView, /* left= */ 0, /* top= */ 0, /* right= */
+ 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
+ 0, /* oldBottom = */ 200);
+
+ verify(mNotificationStackScrollLayoutController).animateNextTopPaddingChange();
+ }
+
+ @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 1edeeff..3cce423 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -18,6 +18,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.KeyEvent
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -118,8 +120,10 @@
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock
lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
+ @Mock lateinit var keyEventInteractor: KeyEventInteractor
private val notificationExpansionRepository = NotificationExpansionRepository()
+ private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -148,6 +152,7 @@
featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
testScope = TestScope()
+ fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
@@ -180,7 +185,7 @@
primaryBouncerToGoneTransitionViewModel,
notificationExpansionRepository,
featureFlags,
- FakeSystemClock(),
+ fakeClock,
BouncerMessageInteractor(
FakeBouncerMessageRepository(),
mock(BouncerMessageFactory::class.java),
@@ -188,7 +193,8 @@
CountDownTimerUtil(),
featureFlags
),
- BouncerLogger(logcatLogBuffer("BouncerLog"))
+ BouncerLogger(logcatLogBuffer("BouncerLog")),
+ keyEventInteractor,
)
underTest.setupExpandedStatusBar()
@@ -328,6 +334,33 @@
}
@Test
+ fun handleDispatchTouchEvent_launchAnimationRunningTimesOut() =
+ testScope.runTest {
+ // GIVEN touch dispatcher in a state that returns true
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(
+ true
+ )
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+
+ // WHEN launch animation is running for 2 seconds
+ fakeClock.setUptimeMillis(10000)
+ underTest.setExpandAnimationRunning(true)
+ fakeClock.advanceTime(2000)
+
+ // THEN touch is ignored
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isFalse()
+
+ // WHEN Launch animation is running for 6 seconds
+ fakeClock.advanceTime(4000)
+
+ // THEN move is ignored, down is handled, and window is notified
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(MOVE_EVENT)).isFalse()
+ assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+ verify(notificationShadeWindowController).setLaunchingActivity(false)
+ }
+
+ @Test
fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() {
// down event should be intercepted by keyguardViewManager
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
@@ -345,8 +378,30 @@
verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
}
+ @Test
+ fun forwardsDispatchKeyEvent() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ interactionEventHandler.dispatchKeyEvent(keyEvent)
+ verify(keyEventInteractor).dispatchKeyEvent(keyEvent)
+ }
+
+ @Test
+ fun forwardsDispatchKeyEventPreIme() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ interactionEventHandler.dispatchKeyEventPreIme(keyEvent)
+ verify(keyEventInteractor).dispatchKeyEventPreIme(keyEvent)
+ }
+
+ @Test
+ fun forwardsInterceptMediaKey() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+ interactionEventHandler.interceptMediaKey(keyEvent)
+ verify(keyEventInteractor).interceptMediaKey(keyEvent)
+ }
+
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
private const val VIEW_BOTTOM = 100
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 829184c..66d48d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -198,7 +199,8 @@
CountDownTimerUtil(),
featureFlags
),
- BouncerLogger(logcatLogBuffer("BouncerLog"))
+ BouncerLogger(logcatLogBuffer("BouncerLog")),
+ Mockito.mock(KeyEventInteractor::class.java),
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 9495fdd..ecaf137 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -227,6 +227,25 @@
}
@Test
+ fun activeClockId_changeAfterPluginConnected() {
+ val plugin1 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
+
+ val plugin2 = FakeClockPlugin()
+ .addClock("clock_3", "clock 3", { mockClock })
+ .addClock("clock_4", "clock 4")
+
+ registry.applySettings(ClockSettings("clock_3", null))
+
+ pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+ assertEquals(DEFAULT_CLOCK_ID, registry.activeClockId)
+
+ pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
+ assertEquals("clock_3", registry.activeClockId)
+ }
+
+ @Test
fun createDefaultClock_pluginDisconnected() {
val plugin1 = FakeClockPlugin()
.addClock("clock_1", "clock 1")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
new file mode 100644
index 0000000..b8792a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 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.systemui.statusbar.notification.icon.ui.viewbinder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
+ @Mock private lateinit var notifListener: NotificationListener
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var wakeUpCoordinator: NotificationWakeUpCoordinator
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var notifMediaManager: NotificationMediaManager
+ @Mock private lateinit var dozeParams: DozeParameters
+ @Mock private lateinit var sectionStyleProvider: SectionStyleProvider
+ @Mock private lateinit var darkIconDispatcher: DarkIconDispatcher
+ @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var screenOffAnimController: ScreenOffAnimationController
+ @Mock private lateinit var bubbles: Bubbles
+ @Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var aodIcons: NotificationIconContainer
+ @Mock private lateinit var featureFlags: FeatureFlags
+
+ private val shelfViewModel = NotificationIconContainerShelfViewModel()
+ private val statusBarViewModel = NotificationIconContainerStatusBarViewModel()
+ private val aodViewModel = NotificationIconContainerAlwaysOnDisplayViewModel()
+
+ private lateinit var underTest: NotificationIconAreaControllerViewBinderWrapperImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ NotificationIconAreaControllerViewBinderWrapperImpl(
+ mContext,
+ statusBarStateController,
+ wakeUpCoordinator,
+ keyguardBypassController,
+ notifMediaManager,
+ notifListener,
+ dozeParams,
+ sectionStyleProvider,
+ Optional.of(bubbles),
+ demoModeController,
+ darkIconDispatcher,
+ featureFlags,
+ statusBarWindowController,
+ screenOffAnimController,
+ shelfViewModel,
+ statusBarViewModel,
+ aodViewModel,
+ )
+ }
+
+ @Test
+ fun testNotificationIcons_settingHideIcons() {
+ underTest.settingsListener.onStatusBarIconsBehaviorChanged(true)
+ assertFalse(underTest.shouldShowLowPriorityIcons())
+ }
+
+ @Test
+ fun testNotificationIcons_settingShowIcons() {
+ underTest.settingsListener.onStatusBarIconsBehaviorChanged(false)
+ assertTrue(underTest.shouldShowLowPriorityIcons())
+ }
+
+ @Test
+ fun testAppearResetsTranslation() {
+ underTest.setupAodIcons(aodIcons)
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+ underTest.appearAodIcons()
+ verify(aodIcons).translationY = 0f
+ verify(aodIcons).alpha = 1.0f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 8545b89..3ad3c15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -18,6 +18,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -29,8 +31,10 @@
import android.app.StatusBarManager;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.os.VibrationEffect;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
+import android.view.HapticFeedbackConstants;
import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -42,6 +46,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
@@ -98,6 +103,7 @@
@Mock private UserTracker mUserTracker;
@Mock private QSHost mQSHost;
@Mock private ActivityStarter mActivityStarter;
+ private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -134,7 +140,8 @@
mCameraLauncherLazy,
mUserTracker,
mQSHost,
- mActivityStarter);
+ mActivityStarter,
+ mFeatureFlags);
when(mUserTracker.getUserHandle()).thenReturn(
UserHandle.of(ActivityManager.getCurrentUser()));
@@ -241,4 +248,24 @@
verifyZeroInteractions(mSystemBarAttributesListener);
}
+
+ @Test
+ public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+ mSbcqCallbacks.vibrateOnNavigationKeyDown();
+
+ verify(mVibratorHelper).vibrate(VibrationEffect.EFFECT_TICK);
+ }
+
+ @Test
+ public void vibrateOnNavigationKeyDown_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+ mSbcqCallbacks.vibrateOnNavigationKeyDown();
+
+ verify(mShadeViewController).performHapticFeedback(
+ HapticFeedbackConstants.GESTURE_START
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index 8e1dcf0..1b8cfd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -48,7 +48,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotificationIconAreaControllerTest extends SysuiTestCase {
+public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase {
@Mock
private NotificationListener mListener;
@@ -70,7 +70,7 @@
StatusBarWindowController mStatusBarWindowController;
@Mock
ScreenOffAnimationController mScreenOffAnimationController;
- private NotificationIconAreaController mController;
+ private LegacyNotificationIconAreaControllerImpl mController;
@Mock
private Bubbles mBubbles;
@Mock private DemoModeController mDemoModeController;
@@ -82,7 +82,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mController = new NotificationIconAreaController(
+ mController = new LegacyNotificationIconAreaControllerImpl(
mContext,
mStatusBarStateController,
mWakeUpCoordinator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 0dc1d9a..6b3bd22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1802,6 +1802,15 @@
assertFalse(ScrimState.UNLOCKED.mAnimateChange);
}
+ @Test
+ public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.onUnlockAnimationFinished();
+ assertAlphaAfterExpansion(mNotificationsScrim, 1f, 1f);
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 21e4f5a..a6a2761 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -32,7 +32,6 @@
import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -480,123 +479,45 @@
@Test
public void ifPortraitHalfOpen_drawVerticallyTop() {
- DevicePostureController devicePostureController = mock(DevicePostureController.class);
- when(devicePostureController.getDevicePosture())
- .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
-
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- devicePostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0 , null);
-
- verify(devicePostureController).addCallback(any());
- dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+ mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
mTestableLooper.processAllMessages(); // let dismiss() finish
setOrientation(Configuration.ORIENTATION_PORTRAIT);
// Call show() to trigger layout updates before verifying position
- dialog.show(SHOW_REASON_UNKNOWN);
+ mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
- int gravity = dialog.getWindowGravity();
+ int gravity = mDialog.getWindowGravity();
assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK);
-
- cleanUp(dialog);
}
@Test
public void ifPortraitAndOpen_drawCenterVertically() {
- DevicePostureController devicePostureController = mock(DevicePostureController.class);
- when(devicePostureController.getDevicePosture())
- .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
-
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- devicePostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0, null);
-
- verify(devicePostureController).addCallback(any());
- dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
+ mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
mTestableLooper.processAllMessages(); // let dismiss() finish
setOrientation(Configuration.ORIENTATION_PORTRAIT);
- dialog.show(SHOW_REASON_UNKNOWN);
+ mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
- int gravity = dialog.getWindowGravity();
+ int gravity = mDialog.getWindowGravity();
assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
-
- cleanUp(dialog);
}
@Test
public void ifLandscapeAndHalfOpen_drawCenterVertically() {
- DevicePostureController devicePostureController = mock(DevicePostureController.class);
- when(devicePostureController.getDevicePosture())
- .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
-
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- devicePostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0, null);
-
- verify(devicePostureController).addCallback(any());
- dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+ mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
mTestableLooper.processAllMessages(); // let dismiss() finish
setOrientation(Configuration.ORIENTATION_LANDSCAPE);
- dialog.show(SHOW_REASON_UNKNOWN);
+ mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
- int gravity = dialog.getWindowGravity();
+ int gravity = mDialog.getWindowGravity();
assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
-
- cleanUp(dialog);
}
@Test
@@ -607,31 +528,9 @@
@Test
public void dialogDestroy_removesPostureControllerCallback() {
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- mPostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0, null);
-
verify(mPostureController, never()).removeCallback(any());
- dialog.destroy();
-
+ mDialog.destroy();
verify(mPostureController).removeCallback(any());
-
- cleanUp(dialog);
}
private void setOrientation(int orientation) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 315972c..f594170 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -1225,15 +1225,13 @@
public ContentCaptureOptions getOptions(@UserIdInt int userId,
@NonNull String packageName) {
boolean isContentCaptureReceiverEnabled;
- boolean isContentProtectionReceiverEnabled;
+ boolean isContentProtectionReceiverEnabled =
+ isContentProtectionReceiverEnabled(userId, packageName);
ArraySet<ComponentName> whitelistedComponents = null;
synchronized (mGlobalWhitelistStateLock) {
isContentCaptureReceiverEnabled =
isContentCaptureReceiverEnabled(userId, packageName);
- isContentProtectionReceiverEnabled =
- isContentProtectionReceiverEnabled(userId, packageName);
-
if (!isContentCaptureReceiverEnabled) {
// Full package is not allowlisted: check individual components next
whitelistedComponents = getWhitelistedComponents(userId, packageName);
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index b67e627..d47a399 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -81,7 +81,7 @@
# when a notification has been clicked
27520 notification_clicked (key|3),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1)
# when a notification action button has been clicked
-27521 notification_action_clicked (key|3),(action_index|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1)
+27521 notification_action_clicked (key|3),(piIdentifier|3),(pendingIntent|3),(action_index|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1)
# when a notification has been canceled
27530 notification_canceled (key|3),(reason|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1),(listener|3)
# replaces 27510 with a row per notification
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9f9e2eb..533ecf1 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -391,7 +391,6 @@
final boolean wasBtScoRequested = isBluetoothScoRequested();
CommunicationRouteClient client;
-
// Save previous client route in case of failure to start BT SCO audio
AudioDeviceAttributes prevClientDevice = null;
boolean prevPrivileged = false;
@@ -1043,7 +1042,7 @@
synchronized (mBluetoothAudioStateLock) {
mBluetoothScoOn = on;
updateAudioHalBluetoothState();
- postUpdateCommunicationRouteClient(eventSource);
+ postUpdateCommunicationRouteClient(isBluetoothScoRequested(), eventSource);
}
}
@@ -1395,8 +1394,10 @@
MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset);
}
- /*package*/ void postUpdateCommunicationRouteClient(String eventSource) {
- sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
+ /*package*/ void postUpdateCommunicationRouteClient(
+ boolean wasBtScoRequested, String eventSource) {
+ sendILMsgNoDelay(MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE,
+ wasBtScoRequested ? 1 : 0, eventSource);
}
/*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
@@ -1708,7 +1709,8 @@
: AudioSystem.STREAM_DEFAULT);
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.HEARING_AID) {
- onUpdateCommunicationRouteClient("setBluetoothActiveDevice");
+ onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+ "setBluetoothActiveDevice");
}
}
}
@@ -1762,9 +1764,11 @@
case MSG_I_SET_MODE_OWNER:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
+ boolean wasBtScoRequested = isBluetoothScoRequested();
mAudioModeOwner = (AudioModeInfo) msg.obj;
if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
- onUpdateCommunicationRouteClient("setNewModeOwner");
+ onUpdateCommunicationRouteClient(
+ wasBtScoRequested, "setNewModeOwner");
}
}
}
@@ -1787,10 +1791,10 @@
}
break;
- case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT:
+ case MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- onUpdateCommunicationRouteClient((String) msg.obj);
+ onUpdateCommunicationRouteClient(msg.arg1 == 1, (String) msg.obj);
}
}
break;
@@ -1973,7 +1977,7 @@
private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
- private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
+ private static final int MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
@@ -2330,16 +2334,20 @@
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
- private void onUpdateCommunicationRouteClient(String eventSource) {
- updateCommunicationRoute(eventSource);
+ private void onUpdateCommunicationRouteClient(boolean wasBtScoRequested, String eventSource) {
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
- + crc + " eventSource: " + eventSource);
+ Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
+ + " wasBtScoRequested: " + wasBtScoRequested + " eventSource: " + eventSource);
}
if (crc != null) {
setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
+ } else {
+ if (!isBluetoothScoRequested() && wasBtScoRequested) {
+ mBtHelper.stopBluetoothSco(eventSource);
+ }
+ updateCommunicationRoute(eventSource);
}
}
@@ -2433,6 +2441,7 @@
List<AudioRecordingConfiguration> recordConfigs) {
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
+ final boolean wasBtScoRequested = isBluetoothScoRequested();
boolean updateCommunicationRoute = false;
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
boolean wasActive = crc.isActive();
@@ -2461,7 +2470,8 @@
}
}
if (updateCommunicationRoute) {
- postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity");
+ postUpdateCommunicationRouteClient(
+ wasBtScoRequested, "updateCommunicationRouteClientsActivity");
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6a73c2b..ceb96ef 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10691,6 +10691,27 @@
@Override
@android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public boolean isCsdAsAFeatureAvailable() {
+ super.isCsdAsAFeatureAvailable_enforcePermission();
+ return mSoundDoseHelper.isCsdAsAFeatureAvailable();
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public boolean isCsdAsAFeatureEnabled() {
+ super.isCsdAsAFeatureEnabled_enforcePermission();
+ return mSoundDoseHelper.isCsdAsAFeatureEnabled();
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void setCsdAsAFeatureEnabled(boolean csdToggleValue) {
+ super.setCsdAsAFeatureEnabled_enforcePermission();
+ mSoundDoseHelper.setCsdAsAFeatureEnabled(csdToggleValue);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
@AudioDeviceCategory int btAudioDeviceCategory) {
super.setBluetoothAudioDeviceCategory_enforcePermission();
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 3560797..a4d26d3 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -329,7 +329,7 @@
default:
break;
}
- if(broadcast) {
+ if (broadcast) {
broadcastScoConnectionState(scoAudioState);
//FIXME: this is to maintain compatibility with deprecated intent
// AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
@@ -718,8 +718,10 @@
checkScoAudioState();
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
// Make sure that the state transitions to CONNECTING even if we cannot initiate
- // the connection.
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ // the connection except if already connected internally
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) {
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ }
switch (mScoAudioState) {
case SCO_STATE_INACTIVE:
mScoAudioMode = scoAudioMode;
@@ -775,7 +777,7 @@
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
break;
case SCO_STATE_ACTIVE_INTERNAL:
- Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
+ // Already in ACTIVE mode, simply return
break;
case SCO_STATE_ACTIVE_EXTERNAL:
/* Confirm SCO Audio connection to requesting app as it is already connected
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 851c5c3..5ebc1c0 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -37,13 +37,11 @@
import android.media.ISoundDoseCallback;
import android.media.SoundDoseRecord;
import android.os.Binder;
-import android.os.HandlerExecutor;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -143,8 +141,6 @@
private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
- private static final String FEATURE_FLAG_ENABLE_CSD = "enable_csd";
-
private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
"CSD updates");
@@ -193,7 +189,15 @@
private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
- private ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories =
+ private final Object mCsdAsAFeatureLock = new Object();
+
+ @GuardedBy("mCsdAsAFeatureLock")
+ private boolean mIsCsdAsAFeatureAvailable = false;
+
+ @GuardedBy("mCsdAsAFeatureLock")
+ private boolean mIsCsdAsAFeatureEnabled = false;
+
+ private final ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories =
new ArrayList<>();
private final Object mCsdStateLock = new Object();
@@ -315,10 +319,6 @@
mAlarmManager = (AlarmManager) mContext.getSystemService(
Context.ALARM_SERVICE);
-
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_MEDIA,
- new HandlerExecutor(mAudioHandler),
- p -> updateCsdEnabled("onPropertiesChanged"));
}
void initSafeVolumes() {
@@ -494,6 +494,38 @@
return false;
}
+ boolean isCsdAsAFeatureAvailable() {
+ synchronized (mCsdAsAFeatureLock) {
+ return mIsCsdAsAFeatureAvailable;
+ }
+ }
+
+ boolean isCsdAsAFeatureEnabled() {
+ synchronized (mCsdAsAFeatureLock) {
+ return mIsCsdAsAFeatureEnabled;
+ }
+ }
+
+ void setCsdAsAFeatureEnabled(boolean csdAsAFeatureEnabled) {
+ boolean doUpdate;
+ synchronized (mCsdAsAFeatureLock) {
+ doUpdate = mIsCsdAsAFeatureEnabled != csdAsAFeatureEnabled && mIsCsdAsAFeatureAvailable;
+ mIsCsdAsAFeatureEnabled = csdAsAFeatureEnabled;
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ mSettings.putSecureIntForUser(mAudioService.getContentResolver(),
+ Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED,
+ mIsCsdAsAFeatureEnabled ? 1 : 0, UserHandle.USER_CURRENT);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ if (doUpdate) {
+ updateCsdEnabled("setCsdAsAFeatureEnabled");
+ }
+ }
+
void setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone) {
if (!mEnableCsd.get()) {
return;
@@ -864,6 +896,13 @@
Log.e(TAG, "Exception while forcing the internal MEL computation", e);
}
+ synchronized (mCsdAsAFeatureLock) {
+ mIsCsdAsAFeatureEnabled = mSettings.getSecureIntForUser(
+ mAudioService.getContentResolver(),
+ Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, 0,
+ UserHandle.USER_CURRENT) != 0;
+ }
+
synchronized (mCsdStateLock) {
if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
@@ -908,18 +947,23 @@
@GuardedBy("mSafeMediaVolumeStateLock")
private void updateSafeMediaVolume_l(String caller) {
- boolean safeMediaVolumeEnabled =
- SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_FORCE, false)
- || (mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_safe_media_volume_enabled)
- && !mEnableCsd.get());
boolean safeMediaVolumeBypass =
- SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_BYPASS, false);
+ SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_BYPASS, false)
+ || mEnableCsd.get();
+ boolean safeMediaVolumeForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_FORCE,
+ false);
+ // we are using the MCC overlaid legacy flag used for the safe volume enablement
+ // to determine whether the MCC enforces any safe hearing standard.
+ boolean mccEnforcedSafeMediaVolume = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_safe_media_volume_enabled);
+
+ boolean safeVolumeEnabled =
+ (mccEnforcedSafeMediaVolume || safeMediaVolumeForce) && !safeMediaVolumeBypass;
// The persisted state is either "disabled" or "active": this is the state applied
// next time we boot and cannot be "inactive"
int persistedState;
- if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
+ if (safeVolumeEnabled) {
persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
// The state can already be "inactive" here if the user has forced it before
// the 30 seconds timeout for forced configuration. In this case we don't reset
@@ -946,22 +990,28 @@
}
private void updateCsdEnabled(String caller) {
- boolean newEnableCsd = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE,
- false);
- if (!newEnableCsd) {
- final String featureFlagEnableCsdValue = DeviceConfig.getProperty(
- DeviceConfig.NAMESPACE_MEDIA,
- FEATURE_FLAG_ENABLE_CSD);
- if (featureFlagEnableCsdValue != null) {
- newEnableCsd = Boolean.parseBoolean(featureFlagEnableCsdValue);
+ boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false);
+ // we are using the MCC overlaid legacy flag used for the safe volume enablement
+ // to determine whether the MCC enforces any safe hearing standard.
+ boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_safe_media_volume_enabled);
+ boolean csdEnable = mContext.getResources().getBoolean(
+ R.bool.config_safe_sound_dosage_enabled);
+ boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce;
+
+ synchronized (mCsdAsAFeatureLock) {
+ if (!mccEnforcedSafeMedia && csdEnable) {
+ mIsCsdAsAFeatureAvailable = true;
+ newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce;
+ Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: "
+ + newEnabledCsd);
} else {
- newEnableCsd = mContext.getResources().getBoolean(
- R.bool.config_safe_sound_dosage_enabled);
+ mIsCsdAsAFeatureAvailable = false;
}
}
- if (mEnableCsd.compareAndSet(!newEnableCsd, newEnableCsd)) {
- Log.i(TAG, caller + ": enable CSD " + newEnableCsd);
+ if (mEnableCsd.compareAndSet(!newEnabledCsd, newEnabledCsd)) {
+ Log.i(TAG, caller + ": enabled CSD " + newEnabledCsd);
initCsd();
synchronized (mSafeMediaVolumeStateLock) {
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index c9fb785..97e5c6f 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -57,7 +57,8 @@
@NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
- @NonNull private AuthenticationStatsPersister mAuthenticationStatsPersister;
+ // TODO(b/295582896): Find a way to make this NonNull
+ @Nullable private AuthenticationStatsPersister mAuthenticationStatsPersister;
@NonNull private BiometricNotification mBiometricNotification;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -92,8 +93,9 @@
/** Update total authentication and rejected attempts. */
public void authenticate(int userId, boolean authenticated) {
- // Initialize mUserAuthenticationStatsMap when authenticate to ensure SharedPreferences
- // are ready for application use and avoid ramdump issue.
+ // SharedPreference is not ready when starting system server, initialize
+ // mUserAuthenticationStatsMap in authentication to ensure SharedPreference
+ // is ready for application use.
if (mUserAuthenticationStatsMap.isEmpty()) {
initializeUserAuthenticationStatsMap();
}
@@ -164,6 +166,9 @@
}
private void onUserRemoved(final int userId) {
+ if (mAuthenticationStatsPersister == null) {
+ initializeUserAuthenticationStatsMap();
+ }
mUserAuthenticationStatsMap.remove(userId);
mAuthenticationStatsPersister.removeFrrStats(userId);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
index 7217956..21e93a8 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -98,7 +98,7 @@
*/
public void removeFrrStats(int userId) {
try {
- // Copy into a new HashSet to avoid iterator exception.
+ // Copy into a new HashSet to allow modification.
Set<String> frrStatsSet = new HashSet<>(readFrrStats());
// Remove the old authentication stat for the user if it exists.
@@ -122,7 +122,7 @@
public void persistFrrStats(int userId, int totalAttempts, int rejectedAttempts,
int enrollmentNotifications, int modality) {
try {
- // Copy into a new HashSet to avoid iterator exception.
+ // Copy into a new HashSet to allow modification.
Set<String> frrStatsSet = new HashSet<>(readFrrStats());
// Remove the old authentication stat for the user if it exists.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 230ba63..2ff695d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -87,11 +87,11 @@
public static void showFaceEnrollNotification(@NonNull Context context) {
final String name =
- context.getString(R.string.face_recalibrate_notification_name);
+ context.getString(R.string.device_unlock_notification_name);
final String title =
- context.getString(R.string.fingerprint_setup_notification_title);
+ context.getString(R.string.alternative_unlock_setup_notification_title);
final String content =
- context.getString(R.string.face_setup_notification_title);
+ context.getString(R.string.alternative_face_setup_notification_content);
final Intent intent = new Intent(FACE_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
@@ -112,11 +112,11 @@
public static void showFingerprintEnrollNotification(@NonNull Context context) {
final String name =
- context.getString(R.string.fingerprint_recalibrate_notification_name);
+ context.getString(R.string.device_unlock_notification_name);
final String title =
- context.getString(R.string.fingerprint_setup_notification_title);
+ context.getString(R.string.alternative_unlock_setup_notification_title);
final String content =
- context.getString(R.string.fingerprint_setup_notification_content);
+ context.getString(R.string.alternative_fp_setup_notification_content);
final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index d57dc47..8642fb8 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -16,6 +16,9 @@
package com.android.server.display;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
@@ -132,12 +135,15 @@
/**
* Returns the default size of the surface associated with the display, or null if the surface
* is not provided for layer mirroring by SurfaceFlinger. For non virtual displays, this will
- * be the actual display device's size.
+ * be the actual display device's size, reflecting the current rotation.
*/
@Nullable
public Point getDisplaySurfaceDefaultSizeLocked() {
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
- return new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+ final boolean isRotated = mCurrentOrientation == ROTATION_90
+ || mCurrentOrientation == ROTATION_270;
+ return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
+ : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
}
/**
@@ -358,7 +364,7 @@
}
boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90
- || mCurrentOrientation == Surface.ROTATION_270);
+ || mCurrentOrientation == ROTATION_270);
DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
viewport.deviceWidth = isRotated ? info.height : info.width;
viewport.deviceHeight = isRotated ? info.width : info.height;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e5965ef..486cd28 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2080,7 +2080,7 @@
/** Loads the refresh rate profiles. */
private void loadRefreshRateZoneProfiles(RefreshRateConfigs refreshRateConfigs) {
- if (refreshRateConfigs == null) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getRefreshRateZoneProfiles() == null) {
return;
}
for (RefreshRateZone zone :
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2a617c5..6509126 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -6132,6 +6132,8 @@
mVisibilityStateComputer.dump(pw);
p.println(" mInFullscreenMode=" + mInFullscreenMode);
p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
+ p.println(" ENABLE_HIDE_IME_CAPTION_BAR="
+ + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR);
p.println(" mSettingsObserver=" + mSettingsObserver);
p.println(" mStylusIds=" + (mStylusIds != null
? Arrays.toString(mStylusIds.toArray()) : ""));
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 15a8c0f..c24e729 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1194,7 +1194,9 @@
mNotificationRecordLogger.log(
NotificationRecordLogger.NotificationEvent.fromAction(actionIndex,
generatedByAssistant, action.isContextual()), r);
- EventLogTags.writeNotificationActionClicked(key, actionIndex,
+ EventLogTags.writeNotificationActionClicked(key,
+ action.actionIntent.getTarget().toString(),
+ action.actionIntent.getIntent().toString(), actionIndex,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
nv.rank, nv.count);
nv.recycle();
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b2d3fca..100c638 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,8 +25,6 @@
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -48,6 +46,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.provider.Settings;
@@ -98,8 +97,7 @@
// the period after which a notification is updated where it can make sound
private static final int MAX_SOUND_DELAY_MS = 2000;
private final StatusBarNotification sbn;
- IActivityManager mAm;
- UriGrantsManagerInternal mUgmInternal;
+ private final UriGrantsManagerInternal mUgmInternal;
final int mTargetSdkVersion;
final int mOriginalFlags;
private final Context mContext;
@@ -223,7 +221,6 @@
this.sbn = sbn;
mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class)
.getPackageTargetSdkVersion(sbn.getPackageName());
- mAm = ActivityManager.getService();
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mOriginalFlags = sbn.getNotification().flags;
mRankingTimeMs = calculateRankingTimeMs(0L);
@@ -1387,18 +1384,27 @@
* Collect all {@link Uri} that should have permission granted to whoever
* will be rendering it.
*/
- protected void calculateGrantableUris() {
- final Notification notification = getNotification();
- notification.visitUris((uri) -> {
- visitGrantableUri(uri, false, false);
- });
+ private void calculateGrantableUris() {
+ Trace.beginSection("NotificationRecord.calculateGrantableUris");
+ try {
+ // We can't grant URI permissions from system.
+ final int sourceUid = getSbn().getUid();
+ if (sourceUid == android.os.Process.SYSTEM_UID) return;
- if (notification.getChannelId() != null) {
- NotificationChannel channel = getChannel();
- if (channel != null) {
- visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ final Notification notification = getNotification();
+ notification.visitUris((uri) -> {
+ visitGrantableUri(uri, false, false);
+ });
+
+ if (notification.getChannelId() != null) {
+ NotificationChannel channel = getChannel();
+ if (channel != null) {
+ visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ }
}
+ } finally {
+ Trace.endSection();
}
}
@@ -1413,13 +1419,14 @@
private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
- // We can't grant Uri permissions from system
- final int sourceUid = getSbn().getUid();
- if (sourceUid == android.os.Process.SYSTEM_UID) return;
+ if (mGrantableUris != null && mGrantableUris.contains(uri)) {
+ return; // already verified this URI
+ }
+ final int sourceUid = getSbn().getUid();
final long ident = Binder.clearCallingIdentity();
try {
- // This will throw SecurityException if caller can't grant
+ // This will throw a SecurityException if the caller can't grant.
mUgmInternal.checkGrantUriPermission(sourceUid, null,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 5a6851e..d0f86c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -224,6 +224,9 @@
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -2684,6 +2687,11 @@
if (isTransferringSplashScreen()) {
return true;
}
+ // Only do transfer after transaction has done when starting window exist.
+ if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+ return true;
+ }
requestCopySplashScreen();
return isTransferringSplashScreen();
}
@@ -2844,11 +2852,14 @@
if (mStartingData == null) {
return;
}
- mStartingData.mWaitForSyncTransactionCommit = false;
- if (mStartingData.mRemoveAfterTransaction) {
- mStartingData.mRemoveAfterTransaction = false;
- removeStartingWindowAnimation(mStartingData.mPrepareRemoveAnimation);
+ final StartingData lastData = mStartingData;
+ lastData.mWaitForSyncTransactionCommit = false;
+ if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
+ removeStartingWindowAnimation(lastData.mPrepareRemoveAnimation);
+ } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
+ removeStartingWindow();
}
+ lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
}
void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -2875,7 +2886,7 @@
if (mStartingData != null) {
if (mStartingData.mWaitForSyncTransactionCommit
|| mTransitionController.inCollectingTransition(startingWindow)) {
- mStartingData.mRemoveAfterTransaction = true;
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
mStartingData.mPrepareRemoveAnimation = prepareAnimation;
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fcf6587..ff2c719 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2596,9 +2596,6 @@
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- // When a task is locked, dismiss the root pinned task if it exists
- mRootWindowContainer.removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
-
getLockTaskController().startLockTaskMode(task, isSystemCaller, callingUid);
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 0c98fb5..830f785 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.content.Context.STATUS_BAR_SERVICE;
@@ -669,6 +670,9 @@
}
}
+ // When a task is locked, dismiss the root pinned task if it exists
+ mSupervisor.mRootWindowContainer.removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
+
// System can only initiate screen pinning, not full lock task mode
ProtoLog.w(WM_DEBUG_LOCKTASK, "%s", isSystemCaller ? "Locking pinned" : "Locking fully");
setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 34806bd..a23547e 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import android.annotation.IntDef;
+
import com.android.server.wm.StartingSurfaceController.StartingSurface;
/**
@@ -23,6 +25,20 @@
*/
public abstract class StartingData {
+ /** Nothing need to do after transaction */
+ static final int AFTER_TRANSACTION_IDLE = 0;
+ /** Remove the starting window directly after transaction done. */
+ static final int AFTER_TRANSACTION_REMOVE_DIRECTLY = 1;
+ /** Do copy splash screen to client after transaction done. */
+ static final int AFTER_TRANSACTION_COPY_TO_CLIENT = 2;
+
+ @IntDef(prefix = { "AFTER_TRANSACTION" }, value = {
+ AFTER_TRANSACTION_IDLE,
+ AFTER_TRANSACTION_REMOVE_DIRECTLY,
+ AFTER_TRANSACTION_COPY_TO_CLIENT,
+ })
+ @interface AfterTransaction {}
+
protected final WindowManagerService mService;
protected final int mTypeParams;
@@ -60,7 +76,7 @@
* This starting window should be removed after applying the start transaction of transition,
* which ensures the app window has shown.
*/
- boolean mRemoveAfterTransaction;
+ @AfterTransaction int mRemoveAfterTransaction;
/** Whether to prepare the removal animation. */
boolean mPrepareRemoveAnimation;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 48cca32..5b466a0 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1124,14 +1124,15 @@
+ "track #%d", transition.getSyncId(), track);
}
}
- if (sync) {
+ transition.mAnimationTrack = track;
+ info.setTrack(track);
+ mTrackCount = Math.max(mTrackCount, track + 1);
+ if (sync && mTrackCount > 1) {
+ // If there are >1 tracks, mark as sync so that all tracks finish.
info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.",
transition.getSyncId());
}
- transition.mAnimationTrack = track;
- info.setTrack(track);
- mTrackCount = Math.max(mTrackCount, track + 1);
}
void updateAnimatingState(SurfaceControl.Transaction t) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index dc92376..ba13c99 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -56,7 +56,7 @@
import android.os.RemoteException;
import android.os.StatFs;
import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.Postsubmit;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -93,7 +93,7 @@
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
-@Presubmit
+@Postsubmit
public class PackageManagerTests extends AndroidTestCase {
private static final boolean localLOGV = true;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
new file mode 100644
index 0000000..4fd8f26
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 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.display;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the {@link DisplayDevice} class.
+ *
+ * Build/Install/Run:
+ * atest DisplayServicesTests:DisplayDeviceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DisplayDeviceTest {
+ private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
+ private static final int WIDTH = 500;
+ private static final int HEIGHT = 900;
+ private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+ private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+
+ @Mock
+ private SurfaceControl.Transaction mMockTransaction;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mDisplayDeviceInfo.width = WIDTH;
+ mDisplayDeviceInfo.height = HEIGHT;
+ mDisplayDeviceInfo.rotation = ROTATION_0;
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation0() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_0, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation180() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_180, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation270() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_270, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
+ }
+
+ private static class FakeDisplayDevice extends DisplayDevice {
+ private final DisplayDeviceInfo mDisplayDeviceInfo;
+
+ FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo) {
+ super(null, null, "", InstrumentationRegistry.getInstrumentation().getContext());
+ mDisplayDeviceInfo = displayDeviceInfo;
+ }
+
+ @Override
+ public boolean hasStableUniqueId() {
+ return false;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return mDisplayDeviceInfo;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 285a20c..746fb53 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -36,6 +36,9 @@
import android.content.res.Resources;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.biometrics.sensors.BiometricNotification;
@@ -47,6 +50,8 @@
import java.io.File;
+@Presubmit
+@SmallTest
public class AuthenticationStatsCollectorTest {
private AuthenticationStatsCollector mAuthenticationStatsCollector;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
index 66a8ff3..dde2a3c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -29,6 +29,9 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
import org.json.JSONException;
import org.json.JSONObject;
@@ -45,6 +48,8 @@
import java.util.List;
import java.util.Set;
+@Presubmit
+@SmallTest
public class AuthenticationStatsPersisterTest {
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index bdee99b..5c35b05 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7752,7 +7752,8 @@
public void testOnNotificationActionClick() {
final int actionIndex = 2;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", null).build();
+ new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
+ mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
final boolean generatedByAssistant = false;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -7776,7 +7777,8 @@
public void testOnAssistantNotificationActionClick() {
final int actionIndex = 1;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", null).build();
+ new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
+ mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
final boolean generatedByAssistant = true;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index fae92d9..f83a1df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -36,7 +36,7 @@
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -44,7 +44,6 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
-import android.app.IActivityManager;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationChannel;
@@ -76,6 +75,7 @@
import com.android.internal.R;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.LocalServices;
import com.android.server.UiServiceTestCase;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -850,84 +850,78 @@
@Test
public void testCalculateGrantableUris_PappProvided() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
channel.setSound(null, null);
Notification n = new Notification.Builder(mContext, channel.getId())
.setSmallIcon(Icon.createWithContentUri(Uri.parse("content://something")))
.build();
StatusBarNotification sbn =
new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.mUgmInternal = ugm;
- try {
- record.calculateGrantableUris();
- fail("App provided uri for p targeting app should throw exception");
- } catch (SecurityException e) {
- // expected
- }
+ assertThrows("App provided uri for p targeting app should throw exception",
+ SecurityException.class,
+ () -> new NotificationRecord(mMockContext, sbn, channel));
}
@Test
public void testCalculateGrantableUris_PappProvided_invalidSound() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
channel.setSound(Uri.parse("content://something"), mock(AudioAttributes.class));
Notification n = mock(Notification.class);
when(n.getChannelId()).thenReturn(channel.getId());
StatusBarNotification sbn =
new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.mUgmInternal = ugm;
- record.calculateGrantableUris();
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
}
@Test
public void testCalculateGrantableUris_PuserOverridden() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
Notification n = mock(Notification.class);
when(n.getChannelId()).thenReturn(channel.getId());
StatusBarNotification sbn =
new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.calculateGrantableUris();
+ new NotificationRecord(mMockContext, sbn, channel); // should not throw
}
@Test
public void testCalculateGrantableUris_prePappProvided() {
- IActivityManager am = mock(IActivityManager.class);
UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
anyInt(), anyInt())).thenThrow(new SecurityException());
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
Notification n = mock(Notification.class);
when(n.getChannelId()).thenReturn(channel.getId());
StatusBarNotification sbn =
new StatusBarNotification(PKG_O, PKG_O, id1, tag1, uid, uid, n, mUser, null, uid);
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- record.mAm = am;
- record.calculateGrantableUris();
- // should not throw
+ new NotificationRecord(mMockContext, sbn, channel); // should not throw
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 7b4392b..56c3ec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -317,7 +317,8 @@
assertEquals("Expected " + numPendingScreenshots + " pending screenshots, got "
+ mDataRequester.getPendingScreenshotCount(),
numPendingScreenshots, mDataRequester.getPendingScreenshotCount());
- assertFalse("Expected request NOT completed", mCallbacks.mRequestCompleted);
+ assertEquals("Expected request NOT completed, unless no pending data",
+ numPendingData == 0 && numPendingScreenshots == 0, mCallbacks.mRequestCompleted);
mGate.countDown();
waitForIdle(mHandler);
assertEquals("Expected " + numReceivedData + " data, received "
@@ -376,14 +377,7 @@
@Override
public void onAssistRequestCompleted() {
- mHandler.post(() -> {
- try {
- mGate.await(10, TimeUnit.SECONDS);
- mRequestCompleted = true;
- } catch (InterruptedException e) {
- Log.e(TAG, "Failed to wait", e);
- }
- });
+ mRequestCompleted = true;
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index b4f1176..d7cd9f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -39,6 +39,7 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_SYNC;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
@@ -2384,6 +2385,37 @@
assertFalse(controller.isCollecting());
}
+ @Test
+ public void testNoSyncFlagIfOneTrack() {
+ final TransitionController controller = mAtm.getTransitionController();
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+ mSyncEngine = createTestBLASTSyncEngine();
+ controller.setSyncEngine(mSyncEngine);
+
+ final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
+ final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
+ final Transition transitC = createTestTransition(TRANSIT_OPEN, controller);
+
+ controller.startCollectOrQueue(transitA, (deferred) -> {});
+ controller.startCollectOrQueue(transitB, (deferred) -> {});
+ controller.startCollectOrQueue(transitC, (deferred) -> {});
+
+ // Verify that, as-long as there is <= 1 track, we won't get a SYNC flag
+ transitA.start();
+ transitA.setAllReady();
+ mSyncEngine.tryFinishForTest(transitA.getSyncId());
+ assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
+ transitB.start();
+ transitB.setAllReady();
+ mSyncEngine.tryFinishForTest(transitB.getSyncId());
+ assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
+ transitC.start();
+ transitC.setAllReady();
+ mSyncEngine.tryFinishForTest(transitC.getSyncId());
+ assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 58da4b43..3d78a1d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -54,6 +54,7 @@
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordDetector;
+import android.service.voice.IDetectorSessionStorageService;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.ISandboxedDetectionService;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -69,6 +70,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.server.LocalServices;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -157,6 +159,8 @@
@NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
@GuardedBy("mLock")
@Nullable private IBinder mAudioFlinger;
+
+ @Nullable private IHotwordRecognitionStatusCallback mHotwordRecognitionCallback;
@GuardedBy("mLock")
private boolean mDebugHotwordLogging = false;
@@ -694,6 +698,7 @@
updateContentCaptureManager(connection);
updateSpeechService(connection);
updateServiceIdentity(connection);
+ updateStorageService(connection);
return connection;
}
}
@@ -910,6 +915,7 @@
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
}
+ mHotwordRecognitionCallback = callback;
mDetectorSessions.put(detectorType, session);
session.initialize(options, sharedMemory);
}
@@ -1035,6 +1041,23 @@
}));
}
+ private void updateStorageService(ServiceConnection connection) {
+ connection.run(service -> {
+ service.registerRemoteStorageService(new IDetectorSessionStorageService.Stub() {
+ @Override
+ public void openFile(String filename, AndroidFuture future)
+ throws RemoteException {
+ Slog.v(TAG, "BinderCallback#onFileOpen");
+ try {
+ mHotwordRecognitionCallback.onOpenFile(filename, future);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ });
+ });
+ }
+
private void addServiceUidForAudioPolicy(int uid) {
mScheduledExecutorService.execute(() -> {
AudioManagerInternal audioManager =
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 1d423ca..2ccc0fa 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -97,6 +97,7 @@
"flickerlib-helpers",
"platform-test-annotations",
"wm-flicker-common-app-helpers",
+ "wm-shell-flicker-utils",
],
data: [
":FlickerTestApp",
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
new file mode 100644
index 0000000..87231c8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 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.wm.flicker.activityembedding.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.common.datatypes.Rect
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.utils.*
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/***
+ * Test entering System SplitScreen with Activity Embedding Split and another app.
+ *
+ * Setup: Launch A|B in split and secondaryApp, return to home.
+ * Transitions: Let AE Split A|B enter splitscreen with secondaryApp. Resulting in A|B|secondaryApp.
+ *
+ * To run this test: `atest FlickerTestsOther:EnterSystemSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSystemSplitTest(flicker: LegacyFlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+
+ private val secondaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds ?:
+ error("Display not found")
+ }
+ transitions {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, testApp, secondaryApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, testApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitLayerBecomesVisible() {
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ testApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+ }
+
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitWindowBecomesVisible() = flicker.appWindowIsVisibleAtEnd(testApp)
+
+ @Presubmit
+ @Test
+ fun secondaryLayerBecomesVisible() {
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+
+ /**
+ * After the transition there should be both ActivityEmbedding activities,
+ * SplitScreenPrimaryActivity and the system split divider on screen.
+ * Verify the layers are in expected sizes.
+ */
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitSurfaceAreEven() {
+ flicker.assertLayersEnd {
+ val leftAELayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val rightAELayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ val secondaryAppLayerRegion =
+ visibleRegion(
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
+ val systemDivider = visibleRegion(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ leftAELayerRegion
+ .plus(rightAELayerRegion.region)
+ .plus(secondaryAppLayerRegion.region)
+ .plus(systemDivider.region)
+ .coversExactly(startDisplayBounds)
+ check { "ActivityEmbeddingSplitHeight" }
+ .that(leftAELayerRegion.region.height)
+ .isEqual(rightAELayerRegion.region.height)
+ check { "SystemSplitHeight" }
+ .that(rightAELayerRegion.region.height)
+ .isEqual(secondaryAppLayerRegion.region.height)
+ // TODO(b/292283182): Remove this special case handling.
+ check { "ActivityEmbeddingSplitWidth" }
+ .that(Math.abs(
+ leftAELayerRegion.region.width - rightAELayerRegion.region.width))
+ .isLower(2)
+ check { "SystemSplitWidth" }
+ .that(Math.abs(secondaryAppLayerRegion.region.width -
+ 2 * rightAELayerRegion.region.width))
+ .isLower(2)
+ }
+ }
+
+ /**
+ * Verify the windows are in expected sizes.
+ */
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitWindowsAreEven() {
+ flicker.assertWmEnd {
+ val leftAEWindowRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val rightAEWindowRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ // There's no window for the divider bar.
+ val secondaryAppLayerRegion =
+ visibleRegion(
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
+ check { "ActivityEmbeddingSplitHeight" }
+ .that(leftAEWindowRegion.region.height)
+ .isEqual(rightAEWindowRegion.region.height)
+ check { "SystemSplitHeight" }
+ .that(rightAEWindowRegion.region.height)
+ .isEqual(secondaryAppLayerRegion.region.height)
+ check { "ActivityEmbeddingSplitWidth" }
+ .that(Math.abs(
+ leftAEWindowRegion.region.width - rightAEWindowRegion.region.width))
+ .isLower(2)
+ check { "SystemSplitWidth" }
+ .that(Math.abs(secondaryAppLayerRegion.region.width -
+ 2 * rightAEWindowRegion.region.width))
+ .isLower(2)
+ }
+ }
+
+ @Ignore("Not applicable to this CUJ.")
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}
\ No newline at end of file