diff options
| author | 2023-08-31 11:54:06 -0700 | |
|---|---|---|
| committer | 2023-08-31 12:36:14 -0700 | |
| commit | 75da5da9cc6de5d43e82a520cbe0690386164866 (patch) | |
| tree | 9f2860c9ca58077ae30b9cc8efba063424b22037 | |
| parent | c8c094355fdc8b1f53a7f4c602c1d33e057aff95 (diff) | |
| parent | 4ee2ba0c249aac3c2889109f30fcda67523f9f03 (diff) | |
Merge UP1A.230905.019
Merged-In: I67149c6faa2766be6d2537f2315dd2734bdd0447
Change-Id: I5d35bf3f01914320970b64392555617b774cfa38
171 files changed, 2940 insertions, 716 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e4db1b734daf..ba26457b9f8a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4226,21 +4226,22 @@ public final class ActivityThread extends ClientTransactionHandler decorView.addView(view); view.requestLayout(); - view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() { + view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { private boolean mHandled = false; @Override - public void onDraw() { + public boolean onPreDraw() { if (mHandled) { - return; + return true; } mHandled = true; // Transfer the splash screen view from shell to client. - // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure - // the client view is ready to show and we can use applyTransactionOnDraw to make - // all transitions happen at the same frame. + // Call syncTransferSplashscreenViewTransaction at the first onPreDraw, so we can + // ensure the client view is ready to show, and can use applyTransactionOnDraw to + // make all transitions happen at the same frame. syncTransferSplashscreenViewTransaction( view, r.token, decorView, startingWindowLeash); - view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); + view.post(() -> view.getViewTreeObserver().removeOnPreDrawListener(this)); + return true; } }); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2eb6ca758970..11dd30b82e78 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2171,6 +2171,10 @@ public class Notification implements Parcelable } } + private void visitUris(@NonNull Consumer<Uri> visitor) { + visitIconUri(visitor, getIcon()); + } + @Override public Action clone() { return new Action( @@ -2856,7 +2860,7 @@ public class Notification implements Parcelable if (actions != null) { for (Action action : actions) { - visitIconUri(visitor, action.getIcon()); + action.visitUris(visitor); } } @@ -2947,6 +2951,11 @@ public class Notification implements Parcelable if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } + + if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { + WearableExtender extender = new WearableExtender(this); + extender.visitUris(visitor); + } } /** @@ -11711,6 +11720,12 @@ public class Notification implements Parcelable mFlags &= ~mask; } } + + private void visitUris(@NonNull Consumer<Uri> visitor) { + for (Action action : mActions) { + action.visitUris(visitor); + } + } } /** diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 785470f2f22e..79b68c1456c7 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -571,6 +571,12 @@ public class NotificationManager { */ public static final int BUBBLE_PREFERENCE_SELECTED = 2; + /** + * Maximum length of the component name of a registered NotificationListenerService. + * @hide + */ + public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500; + @UnsupportedAppUsage private static INotificationManager sService; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0f020fa3c402..02650c65e1a4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -234,6 +234,24 @@ public abstract class PackageManager { "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT"; /** + * Application level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for a privileged system installer to define a list of up to 500 packages that + * should not have their updates owned by any installer. The list must be provided via a default + * XML resource with the following format: + * + * <pre> + * <deny-ownership>PACKAGE_NAME</deny-ownership> + * <deny-ownership>PACKAGE_NAME</deny-ownership> + * </pre> + * + * <b>NOTE:</b> Installers that provide this property will not granted update ownership for any + * packages that they request update ownership of. + * @hide + */ + public static final String PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST = + "android.app.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST"; + + /** * A property value set within the manifest. * <p> * The value of a property will only have a single type, as defined by diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 7d72f2459e15..ed22284ae23d 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2009,13 +2009,25 @@ public class Resources { private int mHashCode = 0; - private boolean containsValue(int resId, boolean force) { + private int findValue(int resId, boolean force) { for (int i = 0; i < mCount; ++i) { if (mResId[i] == resId && mForce[i] == force) { - return true; + return i; } } - return false; + return -1; + } + + private void moveToLast(int index) { + if (index < 0 || index >= mCount - 1) { + return; + } + final int id = mResId[index]; + final boolean force = mForce[index]; + System.arraycopy(mResId, index + 1, mResId, index, mCount - index - 1); + mResId[mCount - 1] = id; + System.arraycopy(mForce, index + 1, mForce, index, mCount - index - 1); + mForce[mCount - 1] = force; } public void append(int resId, boolean force) { @@ -2028,15 +2040,17 @@ public class Resources { } // Some apps tend to keep adding same resources over and over, let's protect from it. - if (containsValue(resId, force)) { - return; + // Note: the order still matters, as the values that come later override the earlier + // ones. + final int index = findValue(resId, force); + if (index >= 0) { + moveToLast(index); + } else { + mResId = GrowingArrayUtils.append(mResId, mCount, resId); + mForce = GrowingArrayUtils.append(mForce, mCount, force); + mCount++; + mHashCode = 31 * (31 * mHashCode + resId) + (force ? 1 : 0); } - - mResId = GrowingArrayUtils.append(mResId, mCount, resId); - mForce = GrowingArrayUtils.append(mForce, mCount, force); - mCount++; - - mHashCode = 31 * (31 * mHashCode + resId) + (force ? 1 : 0); } /** diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java index a7cd168690b4..690dfcf9619b 100644 --- a/core/java/android/content/res/ThemedResourceCache.java +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -137,8 +137,10 @@ abstract class ThemedResourceCache<T> { */ @UnsupportedAppUsage public void onConfigurationChange(@Config int configChanges) { - prune(configChanges); - mGeneration++; + synchronized (this) { + pruneLocked(configChanges); + mGeneration++; + } } /** @@ -214,22 +216,20 @@ abstract class ThemedResourceCache<T> { * simply prune missing weak references * @return {@code true} if the cache is completely empty after pruning */ - private boolean prune(@Config int configChanges) { - synchronized (this) { - if (mThemedEntries != null) { - for (int i = mThemedEntries.size() - 1; i >= 0; i--) { - if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { - mThemedEntries.removeAt(i); - } + private boolean pruneLocked(@Config int configChanges) { + if (mThemedEntries != null) { + for (int i = mThemedEntries.size() - 1; i >= 0; i--) { + if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { + mThemedEntries.removeAt(i); } } + } - pruneEntriesLocked(mNullThemedEntries, configChanges); - pruneEntriesLocked(mUnthemedEntries, configChanges); + pruneEntriesLocked(mNullThemedEntries, configChanges); + pruneEntriesLocked(mUnthemedEntries, configChanges); - return mThemedEntries == null && mNullThemedEntries == null - && mUnthemedEntries == null; - } + return mThemedEntries == null && mNullThemedEntries == null + && mUnthemedEntries == null; } private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries, diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 76efce56dcf0..21e4ab195f28 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1762,6 +1762,15 @@ public final class DisplayManager { * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7 */ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; + + /** + * Key for disabling screen wake locks while apps are in cached state. + * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} + * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace. + * @hide + */ + String KEY_DISABLE_SCREEN_WAKE_LOCKS_WHILE_CACHED = + "disable_screen_wake_locks_while_cached"; } /** diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index dbc1be141571..d9ac4850e924 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -868,6 +868,11 @@ public abstract class WallpaperService extends Service { * This will trigger a {@link #onComputeColors()} call. */ public void notifyColorsChanged() { + if (mDestroyed) { + Log.i(TAG, "Ignoring notifyColorsChanged(), Engine has already been destroyed."); + return; + } + final long now = mClockFunction.get(); if (now - mLastColorInvalidation < NOTIFY_COLORS_RATE_LIMIT_MS) { Log.w(TAG, "This call has been deferred. You should only call " @@ -2226,7 +2231,11 @@ public abstract class WallpaperService extends Service { } } - void detach() { + /** + * @hide + */ + @VisibleForTesting + public void detach() { if (mDestroyed) { return; } @@ -2442,6 +2451,14 @@ public abstract class WallpaperService extends Service { } public void reportShown() { + if (mEngine == null) { + Log.i(TAG, "Can't report null engine as shown."); + return; + } + if (mEngine.mDestroyed) { + Log.i(TAG, "Engine was destroyed before we could draw."); + return; + } if (!mShownReported) { mShownReported = true; Trace.beginSection("WPMS.mConnection.engineShown"); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 41ef44e1ac1f..3cac1e5f7d6e 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2657,17 +2657,6 @@ public final class InputMethodManager { } } - if (windowGainingFocus == null) { - windowGainingFocus = view.getWindowToken(); - if (windowGainingFocus == null) { - Log.e(TAG, "ABORT input: ServedView must be attached to a Window"); - return false; - } - startInputFlags = getStartInputFlags(view, startInputFlags); - softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode; - windowFlags = view.getViewRootImpl().mWindowAttributes.flags; - } - // Now we need to get an input connection from the served view. // This is complicated in a couple ways: we can't be holding our lock // when calling out to the view, and we need to make sure we call into @@ -2690,6 +2679,17 @@ public final class InputMethodManager { return false; } + if (windowGainingFocus == null) { + windowGainingFocus = view.getWindowToken(); + if (windowGainingFocus == null) { + Log.e(TAG, "ABORT input: ServedView must be attached to a Window"); + return false; + } + startInputFlags = getStartInputFlags(view, startInputFlags); + softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode; + windowFlags = view.getViewRootImpl().mWindowAttributes.flags; + } + // Okay we are now ready to call into the served view and have it // do its stuff. // Life is good: let's hook everything up! diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index b65c1a17e26b..cb5dbe6c5618 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -1550,6 +1550,7 @@ public class ScrollView extends FrameLayout { float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size; int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR * mEdgeGlowTop.onPullDistance(deltaDistance, 0.5f)); + mEdgeGlowTop.onRelease(); if (consumed != unconsumed) { mEdgeGlowTop.finish(); } @@ -1560,6 +1561,7 @@ public class ScrollView extends FrameLayout { float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size; int consumed = Math.round(size / FLING_DESTRETCH_FACTOR * mEdgeGlowBottom.onPullDistance(deltaDistance, 0.5f)); + mEdgeGlowBottom.onRelease(); if (consumed != unconsumed) { mEdgeGlowBottom.finish(); } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 7452daa4908c..65b59790e327 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -56,6 +56,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.telecom.TelecomManager; +import android.util.Log; import android.util.Slog; import android.view.View; import android.widget.Button; @@ -124,16 +125,19 @@ public class IntentForwarderActivity extends Activity { String className = intentReceived.getComponent().getClassName(); final int targetUserId; final String userMessage; + final UserInfo managedProfile; if (className.equals(FORWARD_INTENT_TO_PARENT)) { userMessage = getForwardToPersonalMessage(); targetUserId = getProfileParent(); + managedProfile = null; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) .setSubtype(MetricsEvent.PARENT_PROFILE)); } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { userMessage = getForwardToWorkMessage(); - targetUserId = getManagedProfile(); + managedProfile = getManagedProfile(); + targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) @@ -142,6 +146,7 @@ public class IntentForwarderActivity extends Activity { Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); userMessage = null; targetUserId = UserHandle.USER_NULL; + managedProfile = null; } if (targetUserId == UserHandle.USER_NULL) { // This covers the case where there is no parent / managed profile. @@ -185,27 +190,49 @@ public class IntentForwarderActivity extends Activity { finish(); // When switching to the work profile, ask the user for consent before launching } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { - maybeShowUserConsentMiniResolver(result, newIntent, targetUserId); + maybeShowUserConsentMiniResolver(result, newIntent, managedProfile); } }, getApplicationContext().getMainExecutor()); } private void maybeShowUserConsentMiniResolver( - ResolveInfo target, Intent launchIntent, int targetUserId) { + ResolveInfo target, Intent launchIntent, UserInfo managedProfile) { if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { finish(); return; } - if (launchIntent.getBooleanExtra(EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) - && getCallingPackage() != null - && PERMISSION_GRANTED == getPackageManager().checkPermission( - INTERACT_ACROSS_USERS, getCallingPackage())) { + int targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; + String callingPackage = getCallingPackage(); + boolean privilegedCallerAskedToSkipUserConsent = + launchIntent.getBooleanExtra( + EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) + && callingPackage != null + && PERMISSION_GRANTED == getPackageManager().checkPermission( + INTERACT_ACROSS_USERS, callingPackage); + + DevicePolicyManager devicePolicyManager = + getSystemService(DevicePolicyManager.class); + ComponentName profileOwnerName = devicePolicyManager.getProfileOwnerAsUser(targetUserId); + boolean intentToLaunchProfileOwner = profileOwnerName != null + && profileOwnerName.getPackageName().equals(target.getComponentInfo().packageName); + + if (privilegedCallerAskedToSkipUserConsent || intentToLaunchProfileOwner) { + Log.i("IntentForwarderActivity", String.format( + "Skipping user consent for redirection into the managed profile for intent [%s]" + + ", privilegedCallerAskedToSkipUserConsent=[%s]" + + ", intentToLaunchProfileOwner=[%s]", + launchIntent, privilegedCallerAskedToSkipUserConsent, + intentToLaunchProfileOwner)); startActivityAsCaller(launchIntent, targetUserId); finish(); return; } + Log.i("IntentForwarderActivity", String.format( + "Showing user consent for redirection into the managed profile for intent [%s] and " + + " calling package [%s]", + launchIntent, callingPackage)); int layoutId = R.layout.miniresolver; setContentView(layoutId); @@ -245,8 +272,7 @@ public class IntentForwarderActivity extends Activity { View telephonyInfo = findViewById(R.id.miniresolver_info_section); - DevicePolicyManager devicePolicyManager = - getSystemService(DevicePolicyManager.class); + // Additional information section is work telephony specific. Therefore, it is only shown // for telephony related intents, when all sim subscriptions are in the work profile. if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent)) @@ -507,20 +533,18 @@ public class IntentForwarderActivity extends Activity { } /** - * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is - * no managed profile. + * Returns the managed profile for this device or null if there is no managed profile. * - * TODO: Remove the assumption that there is only one managed profile - * on the device. + * TODO: Remove the assumption that there is only one managed profile on the device. */ - private int getManagedProfile() { + @Nullable private UserInfo getManagedProfile() { List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); for (UserInfo userInfo : relatedUsers) { - if (userInfo.isManagedProfile()) return userInfo.id; + if (userInfo.isManagedProfile()) return userInfo; } Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE + " has been called, but there is no managed profile"); - return UserHandle.USER_NULL; + return null; } /** diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index ca3373c55588..490ec35d1482 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1008,7 +1008,7 @@ public class LockPatternUtils { CREDENTIAL_TYPE_API, CREDENTIAL_TYPE_API, mCredentialTypeQuery); /** - * Invalidate the credential cache + * Invalidate the credential type cache * @hide */ public final static void invalidateCredentialTypeCache() { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 52cc35d0d86e..0b67b6ca734f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2560,6 +2560,17 @@ assistant activities (ACTIVITY_TYPE_ASSISTANT) --> <bool name="config_dismissDreamOnActivityStart">false</bool> + <!-- Whether to send a user activity event to PowerManager when a dream quits unexpectedly so + that the screen won't immediately shut off. + + When a dream stops unexpectedly, such as due to an app update, if the device has been + inactive less than the user's screen timeout, the device goes to keyguard and times out + back to dreaming after a few seconds. If the device has been inactive longer, the screen + will immediately turn off. With this flag on, the device will go back to keyguard in all + scenarios rather than turning off, which gives the device a chance to start dreaming + again. --> + <bool name="config_resetScreenTimeoutOnUnexpectedDreamExit">false</bool> + <!-- The prefixes of dream component names that are loggable. Matched against ComponentName#flattenToString() for dream components. If empty, logs "other" for all. --> @@ -2805,12 +2816,14 @@ <bool name="config_multiuserDelayUserDataLocking">false</bool> <!-- Whether the device allows users to start in background visible on displays. - Should be false for most devices, except automotive vehicle with passenger displays. --> + Should be false for all devices in production. Can be enabled only for development use + in automotive vehicles with passenger displays. --> <bool name="config_multiuserVisibleBackgroundUsers">false</bool> <!-- Whether the device allows users to start in background visible on the default display. - Should be false for most devices, except passenger-only automotive build (i.e., when - Android runs in a separate system in the back seat to manage the passenger displays). + Should be false for all devices in production. Can be enabled only for development use + in passenger-only automotive build (i.e., when Android runs in a separate system in the + back seat to manage the passenger displays). When set to true, config_multiuserVisibleBackgroundUsers must also be true. --> <bool name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay">false</bool> @@ -5178,7 +5191,7 @@ </string-array> <!-- The integer index of the selected option in config_udfps_touch_detection_options --> - <integer name="config_selected_udfps_touch_detection">3</integer> + <integer name="config_selected_udfps_touch_detection">0</integer> <!-- An array of arrays of side fingerprint sensor properties relative to each display. Note: this value is temporary and is expected to be queried directly diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1ccee27dcaa5..63bfa13387b6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2207,6 +2207,7 @@ <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> + <java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" /> <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" /> <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" /> <java-symbol type="integer" name="config_minDreamOverlayDurationMs" /> 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 b14c3c10846b..08da4857a0b0 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 @@ -1927,6 +1927,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, pw.println(innerPrefix + "mLeash=" + mLeash); pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState()); pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams); + mPipTransitionController.dump(pw, innerPrefix); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index b8407c465741..e3d53fc415db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -72,6 +72,7 @@ import com.android.wm.shell.transition.CounterRotatorHelper; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.TransitionUtil; +import java.io.PrintWriter; import java.util.Optional; /** @@ -339,19 +340,36 @@ public class PipTransition extends PipTransitionController { } // This means an expand happened before enter-pip finished and we are now "merging" a // no-op transition that happens to match our exit-pip. + // Or that the keyguard is up and preventing the transition from applying, in which case we + // want to manually reset pip. (b/283783868) boolean cancelled = false; if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); cancelled = true; } + // Unset exitTransition AFTER cancel so that finishResize knows we are merging. mExitTransition = null; - if (!cancelled || aborted) return; + if (!cancelled) return; final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (taskInfo != null) { - startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), - mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), - new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + if (aborted) { + // keyguard case - the transition got aborted, so we want to reset state and + // windowing mode before reapplying the resize transaction + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + mPipOrganizer.onExitPipFinished(taskInfo); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); + wct.setBounds(taskInfo.token, null); + mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false); + } else { + // merge case + startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), + mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), + new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + } } mExitDestinationBounds.setEmpty(); mCurrentPipTaskToken = null; @@ -434,6 +452,9 @@ public class PipTransition extends PipTransitionController { @Override public void forceFinishTransition() { + // mFinishCallback might be null with an outdated mCurrentPipTaskToken + // for example, when app crashes while in PiP and exit transition has not started + mCurrentPipTaskToken = null; if (mFinishCallback == null) return; mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); mFinishCallback = null; @@ -567,7 +588,16 @@ public class PipTransition extends PipTransitionController { mPipBoundsState.getDisplayBounds()); mFinishCallback = (wct, wctCB) -> { mPipOrganizer.onExitPipFinished(taskInfo); - if (!Transitions.SHELL_TRANSITIONS_ROTATION && toFullscreen) { + + // TODO(b/286346098): remove the OPEN app flicker completely + // not checking if we go to fullscreen helps avoid getting pip into an inconsistent + // state after the flicker occurs. This is a temp solution until flicker is removed. + if (!Transitions.SHELL_TRANSITIONS_ROTATION) { + // will help to debug the case when we are not exiting to fullscreen + if (!toFullscreen) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: startExitAnimation() not exiting to fullscreen", TAG); + } wct = wct != null ? wct : new WindowContainerTransaction(); wct.setBounds(pipTaskToken, null); mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); @@ -831,7 +861,7 @@ public class PipTransition extends PipTransitionController { } final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); - final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); + final Rect currentBounds = pipChange.getStartAbsBounds(); int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds, destinationBounds); @@ -856,6 +886,9 @@ public class PipTransition extends PipTransitionController { final int enterAnimationType = mEnterAnimationType; if (enterAnimationType == ANIM_TYPE_ALPHA) { startTransaction.setAlpha(leash, 0f); + } else { + // set alpha to 1, because for multi-activity PiP it will create a new task with alpha 0 + startTransaction.setAlpha(leash, 1f); } startTransaction.apply(); @@ -1047,7 +1080,7 @@ public class PipTransition extends PipTransitionController { // When the PIP window is visible and being a part of the transition, such as display // rotation, we need to update its bounds and rounded corner. final SurfaceControl leash = pipChange.getLeash(); - final Rect destBounds = mPipBoundsState.getBounds(); + final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds(); final boolean isInPip = mPipTransitionState.isInPip(); mSurfaceTransactionHelper .crop(startTransaction, leash, destBounds) @@ -1108,4 +1141,12 @@ public class PipTransition extends PipTransitionController { PipMenuController.ALPHA_NO_CHANGE); mPipMenuController.updateMenuBounds(destinationBounds); } + + @Override + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "mCurrentPipTaskToken=" + mCurrentPipTaskToken); + pw.println(innerPrefix + "mFinishCallback=" + mFinishCallback); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index e1bcd70c256b..63627938ec87 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -42,6 +42,7 @@ import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -283,4 +284,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH */ void onPipTransitionCanceled(int direction); } + + /** + * Dumps internal states. + */ + public void dump(PrintWriter pw, String prefix) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 01d8967c7d60..5db872bb4550 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2530,8 +2530,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // handling to the mixed-handler to deal with splitting it up. if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, startTransaction, finishTransaction, finishCallback)) { - mSplitLayout.update(startTransaction); - startTransaction.apply(); + if (mSplitTransitions.isPendingResize(transition)) { + // Only need to update in resize because divider exist before transition. + mSplitLayout.update(startTransaction); + startTransaction.apply(); + } return true; } } @@ -2828,18 +2831,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + final ArrayMap<Integer, SurfaceControl> dismissingTasks = new ArrayMap<>(); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null) continue; + if (getStageOfTask(taskInfo) != null + || getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) { + dismissingTasks.put(taskInfo.taskId, change.getLeash()); + } + } + + if (shouldBreakPairedTaskInRecents(dismissReason)) { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again mRecentTasks.ifPresent(recentTasks -> { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null && (getStageOfTask(taskInfo) != null - || getSplitItemPosition(change.getLastParent()) - != SPLIT_POSITION_UNDEFINED)) { - recentTasks.removeSplitPair(taskInfo.taskId); - } + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + recentTasks.removeSplitPair(dismissingTasks.keyAt(i)); } }); } @@ -2857,6 +2866,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); t.setPosition(toStage == STAGE_TYPE_MAIN ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); + } else { + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + finishT.hide(dismissingTasks.valueAt(i)); + } } if (toStage == STAGE_TYPE_UNDEFINED) { @@ -2866,7 +2879,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // Hide divider and dim layer on transition finished. - setDividerVisibility(false, finishT); + setDividerVisibility(false, t); finishT.hide(mMainStage.mDimLayer); finishT.hide(mSideStage.mDimLayer); } @@ -2890,8 +2903,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.getSplitDecorManager().release(callbackT); callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); }); - - addDividerBarToTransition(info, false /* show */); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index c964df1452e0..c2f15f6cba75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -16,6 +16,7 @@ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,13 @@ public class TaskSnapshotWindow { @NonNull Runnable clearWindowHandler) { final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; final int taskId = runningTaskInfo.taskId; + + // if we're in PIP we don't want to create the snapshot + if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "did not create taskSnapshot due to being in PIP"); + return null; + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index e83780a6fc77..8357072d6a78 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -25,6 +25,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; @@ -476,6 +477,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } } + mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, finishCB); // Dispatch the rest of the transition normally. This will most-likely be taken by diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt index 26efb55fa560..473603002b21 100644 --- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt @@ -111,8 +111,14 @@ class LowLightTransitionCoordinator @Inject constructor() { } animator.addListener(listener) continuation.invokeOnCancellation { - animator.removeListener(listener) - animator.cancel() + try { + animator.removeListener(listener) + animator.cancel() + } catch (exception: IndexOutOfBoundsException) { + // TODO(b/285666217): remove this try/catch once a proper fix is implemented. + // Cancelling the animator can cause an exception since we may be removing a + // listener during the cancellation. See b/285666217 for more details. + } } } } diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index 4c9a23d3fde0..740988f77270 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -90,8 +90,8 @@ public: requireUnpremul, prefColorSpace); } - bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, const SkIRect& desiredSubset, - int sampleSize, bool requireUnpremul) { + bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, int outWidth, int outHeight, + const SkIRect& desiredSubset, int sampleSize, bool requireUnpremul) { SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType); sk_sp<SkColorSpace> decodeColorSpace = mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr); @@ -109,9 +109,8 @@ public: // kPremul_SkAlphaType is used just as a placeholder as it doesn't change the underlying // allocation type. RecyclingClippingPixelAllocator will populate this with the // actual alpha type in either allocPixelRef() or copyIfNecessary() - sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap( - SkImageInfo::Make(desiredSubset.width(), desiredSubset.height(), decodeColorType, - kPremul_SkAlphaType, decodeColorSpace)); + sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make( + outWidth, outHeight, decodeColorType, kPremul_SkAlphaType, decodeColorSpace)); if (!nativeBitmap) { ALOGE("OOM allocating Bitmap for Gainmap"); return false; @@ -134,9 +133,12 @@ public: return true; } - SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion) { + SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion, int* inOutWidth, + int* inOutHeight) { const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width(); const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height(); + *inOutWidth *= scaleX; + *inOutHeight *= scaleY; // TODO: Account for rounding error? return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY, mainImageRegion.right() * scaleX, @@ -328,21 +330,16 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in sp<uirenderer::Gainmap> gainmap; bool hasGainmap = brd->hasGainmap(); if (hasGainmap) { - SkIRect adjustedSubset{}; + int gainmapWidth = bitmap.width(); + int gainmapHeight = bitmap.height(); if (javaBitmap) { - // Clamp to the width/height of the recycled bitmap in case the reused bitmap - // was too small for the specified rectangle, in which case we need to clip - adjustedSubset = SkIRect::MakeXYWH(inputX, inputY, - std::min(subset.width(), recycledBitmap->width()), - std::min(subset.height(), recycledBitmap->height())); - } else { - // We are not recycling, so use the decoded width/height for calculating the gainmap - // subset instead to ensure the gainmap region proportionally matches - adjustedSubset = SkIRect::MakeXYWH(std::max(0, inputX), std::max(0, inputY), - bitmap.width(), bitmap.height()); + // If we are recycling we must match the inBitmap's relative dimensions + gainmapWidth = recycledBitmap->width(); + gainmapHeight = recycledBitmap->height(); } - SkIRect gainmapSubset = brd->calculateGainmapRegion(adjustedSubset); - if (!brd->decodeGainmapRegion(&gainmap, gainmapSubset, sampleSize, requireUnpremul)) { + SkIRect gainmapSubset = brd->calculateGainmapRegion(subset, &gainmapWidth, &gainmapHeight); + if (!brd->decodeGainmapRegion(&gainmap, gainmapWidth, gainmapHeight, gainmapSubset, + sampleSize, requireUnpremul)) { // If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap hasGainmap = false; } diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index d4e919fefbbd..46698a6fdcc0 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -40,7 +40,7 @@ namespace android { namespace uirenderer { namespace renderthread { -static std::array<std::string_view, 19> sEnableExtensions{ +static std::array<std::string_view, 20> sEnableExtensions{ VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, @@ -60,6 +60,7 @@ static std::array<std::string_view, 19> sEnableExtensions{ VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, + VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, }; static bool shouldEnableExtension(const std::string_view& extension) { @@ -657,7 +658,6 @@ void VulkanManager::destroySurface(VulkanSurface* surface) { if (VK_NULL_HANDLE != mGraphicsQueue) { mQueueWaitIdle(mGraphicsQueue); } - mDeviceWaitIdle(mDevice); delete surface; } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 0e9c162e4929..651c732abdb8 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -156,6 +156,7 @@ public final class MediaRoute2Info implements Parcelable { TYPE_REMOTE_GAME_CONSOLE, TYPE_REMOTE_CAR, TYPE_REMOTE_SMARTWATCH, + TYPE_REMOTE_SMARTPHONE, TYPE_GROUP }) @Retention(RetentionPolicy.SOURCE) @@ -343,6 +344,17 @@ public final class MediaRoute2Info implements Parcelable { public static final int TYPE_REMOTE_SMARTWATCH = 1009; /** + * Indicates the route is a remote smartphone. + * + * <p>A remote device uses a routing protocol managed by the application, as opposed to the + * routing being done by the system. + * + * @see #getType + * @hide + */ + public static final int TYPE_REMOTE_SMARTPHONE = 1010; + + /** * Indicates the route is a group of devices. * * @see #getType @@ -546,32 +558,8 @@ public final class MediaRoute2Info implements Parcelable { return mFeatures; } - // TODO (b/278728942): Add the following once the symbols are published in the SDK. Until then, - // adding them would cause the generated link to be broken. - // @see #TYPE_REMOTE_TABLET - // @see #TYPE_REMOTE_TABLET_DOCKED - // @see #TYPE_REMOTE_COMPUTER - // @see #TYPE_REMOTE_GAME_CONSOLE - // @see #TYPE_REMOTE_CAR - // @see #TYPE_REMOTE_SMARTWATCH /** * Returns the type of this route. - * - * @see #TYPE_UNKNOWN - * @see #TYPE_BUILTIN_SPEAKER - * @see #TYPE_WIRED_HEADSET - * @see #TYPE_WIRED_HEADPHONES - * @see #TYPE_BLUETOOTH_A2DP - * @see #TYPE_HDMI - * @see #TYPE_DOCK - * @see #TYPE_USB_DEVICE - * @see #TYPE_USB_ACCESSORY - * @see #TYPE_USB_HEADSET - * @see #TYPE_HEARING_AID - * @see #TYPE_REMOTE_TV - * @see #TYPE_REMOTE_SPEAKER - * @see #TYPE_REMOTE_AUDIO_VIDEO_RECEIVER - * @see #TYPE_GROUP */ @Type public int getType() { @@ -954,6 +942,8 @@ public final class MediaRoute2Info implements Parcelable { return "REMOTE_CAR"; case TYPE_REMOTE_SMARTWATCH: return "REMOTE_SMARTWATCH"; + case TYPE_REMOTE_SMARTPHONE: + return "REMOTE_SMARTPHONE"; case TYPE_GROUP: return "GROUP"; case TYPE_UNKNOWN: diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index b5e4fa38d244..af06d7304160 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -243,7 +243,9 @@ public class RestrictedSwitchPreference extends SwitchPreference { return mHelper != null ? mHelper.packageName : null; } - public void updateState(@NonNull String packageName, int uid, boolean isEnabled) { + /** Updates enabled state based on associated package. */ + public void updateState( + @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) { mHelper.updatePackageDetails(packageName, uid); if (mAppOpsManager == null) { mAppOpsManager = getContext().getSystemService(AppOpsManager.class); @@ -254,7 +256,9 @@ public class RestrictedSwitchPreference extends SwitchPreference { final boolean ecmEnabled = getContext().getResources().getBoolean( com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; - if (isEnabled) { + if (!isEnableAllowed && !isEnabled) { + setEnabled(false); + } else if (isEnabled) { setEnabled(true); } else if (appOpsAllowed && isDisabledByAppOps()) { setEnabled(true); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index cd6609ec463e..963bd9daa975 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -15,6 +15,8 @@ */ package com.android.settingslib.media; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; @@ -22,6 +24,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -39,7 +42,13 @@ public class BluetoothMediaDevice extends MediaDevice { BluetoothMediaDevice(Context context, CachedBluetoothDevice device, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, device, routerManager, info, packageName, null); + } + + BluetoothMediaDevice(Context context, CachedBluetoothDevice device, + MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName, + RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mCachedDevice = device; mAudioManager = context.getSystemService(AudioManager.class); initDeviceRecord(); @@ -58,6 +67,12 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public Drawable getIcon() { return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice()) ? mContext.getDrawable(R.drawable.ic_earbuds_advanced) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 632120e77c87..b10d7946f57f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -19,6 +19,7 @@ import static android.media.MediaRoute2Info.TYPE_GROUP; import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR; import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER; import static android.media.MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE; +import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTPHONE; import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTWATCH; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET; @@ -103,6 +104,9 @@ public class InfoMediaDevice extends MediaDevice { case TYPE_REMOTE_SMARTWATCH: resId = R.drawable.ic_media_smartwatch; break; + case TYPE_REMOTE_SMARTPHONE: + resId = R.drawable.ic_smartphone; + break; case TYPE_REMOTE_SPEAKER: default: resId = R.drawable.ic_media_speaker_device; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index ffc0479f01c3..1728e405fa29 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -26,6 +26,7 @@ import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER; import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR; import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER; import static android.media.MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE; +import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTPHONE; import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTWATCH; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET; @@ -90,6 +91,7 @@ public class InfoMediaManager extends MediaManager { MediaRouter2Manager mRouterManager; @VisibleForTesting String mPackageName; + boolean mIsScanning = false; private MediaDevice mCurrentConnectedDevice; private LocalBluetoothManager mBluetoothManager; @@ -109,22 +111,29 @@ public class InfoMediaManager extends MediaManager { @Override public void startScan() { - mMediaDevices.clear(); - mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); - mRouterManager.registerScanRequest(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE - && !TextUtils.isEmpty(mPackageName)) { - RouteListingPreference routeListingPreference = - mRouterManager.getRouteListingPreference(mPackageName); - Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap); + if (!mIsScanning) { + mMediaDevices.clear(); + mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); + mRouterManager.registerScanRequest(); + mIsScanning = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE + && !TextUtils.isEmpty(mPackageName)) { + RouteListingPreference routeListingPreference = + mRouterManager.getRouteListingPreference(mPackageName); + Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, + mPreferenceItemMap); + } + refreshDevices(); } - refreshDevices(); } @Override public void stopScan() { - mRouterManager.unregisterCallback(mMediaRouterCallback); - mRouterManager.unregisterScanRequest(); + if (mIsScanning) { + mRouterManager.unregisterCallback(mMediaRouterCallback); + mRouterManager.unregisterScanRequest(); + mIsScanning = false; + } } /** @@ -547,6 +556,7 @@ public class InfoMediaManager extends MediaManager { case TYPE_REMOTE_GAME_CONSOLE: case TYPE_REMOTE_CAR: case TYPE_REMOTE_SMARTWATCH: + case TYPE_REMOTE_SMARTPHONE: mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route, mPackageName, mPreferenceItemMap.get(route.getId())); break; @@ -558,8 +568,10 @@ public class InfoMediaManager extends MediaManager { case TYPE_HDMI: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: - mediaDevice = - new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName); + mediaDevice = mPreferenceItemMap.containsKey(route.getId()) ? new PhoneMediaDevice( + mContext, mRouterManager, route, mPackageName, + mPreferenceItemMap.get(route.getId())) : new PhoneMediaDevice(mContext, + mRouterManager, route, mPackageName); break; case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: @@ -569,8 +581,11 @@ public class InfoMediaManager extends MediaManager { final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { - mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, - route, mPackageName); + mediaDevice = mPreferenceItemMap.containsKey(route.getId()) + ? new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName, mPreferenceItemMap.get(route.getId())) + : new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName); } break; case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER: @@ -699,20 +714,19 @@ public class InfoMediaManager extends MediaManager { List<MediaRoute2Info> selectedRouteInfos, List<MediaRoute2Info> infolist, List<RouteListingPreference.Item> preferenceRouteListing) { final List<MediaRoute2Info> sortedInfoList = new ArrayList<>(selectedRouteInfos); + infolist.removeAll(selectedRouteInfos); + sortedInfoList.addAll(infolist.stream().filter( + MediaRoute2Info::isSystemRoute).collect(Collectors.toList())); for (RouteListingPreference.Item item : preferenceRouteListing) { for (MediaRoute2Info info : infolist) { if (item.getRouteId().equals(info.getId()) - && !selectedRouteInfos.contains(info)) { + && !selectedRouteInfos.contains(info) + && !info.isSystemRoute()) { sortedInfoList.add(info); break; } } } - if (sortedInfoList.size() != infolist.size()) { - infolist.removeAll(sortedInfoList); - sortedInfoList.addAll(infolist.stream().filter( - MediaRoute2Info::isSystemRoute).collect(Collectors.toList())); - } return sortedInfoList; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 34519c993d27..accd88c2bfe3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -24,10 +24,13 @@ import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import androidx.annotation.VisibleForTesting; @@ -51,7 +54,12 @@ public class PhoneMediaDevice extends MediaDevice { PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, routerManager, info, packageName, null); + } + + PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, + String packageName, RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mDeviceIconUtil = new DeviceIconUtil(); initDeviceRecord(); } @@ -86,6 +94,12 @@ public class PhoneMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public String getSummary() { return mSummary; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java index 67a045e9a449..19a3db25996e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java @@ -20,6 +20,7 @@ import static android.media.MediaRoute2Info.TYPE_GROUP; import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR; import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER; import static android.media.MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE; +import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTPHONE; import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTWATCH; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET; @@ -143,5 +144,9 @@ public class InfoMediaDeviceTest { assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo( R.drawable.ic_media_smartwatch); + + when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_SMARTPHONE); + + assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(R.drawable.ic_smartphone); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 39780f3a96ed..7b8815e7c05c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -114,6 +114,23 @@ public class InfoMediaManagerTest { } @Test + public void stopScan_notStartFirst_notCallsUnregister() { + mInfoMediaManager.mRouterManager = mRouterManager; + mInfoMediaManager.stopScan(); + + verify(mRouterManager, never()).unregisterScanRequest(); + } + + @Test + public void stopScan_startFirst_callsUnregister() { + mInfoMediaManager.mRouterManager = mRouterManager; + mInfoMediaManager.startScan(); + mInfoMediaManager.stopScan(); + + verify(mRouterManager).unregisterScanRequest(); + } + + @Test public void onRouteAdded_getAvailableRoutes_shouldAddMediaDevice() { final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); @@ -327,11 +344,12 @@ public class InfoMediaManagerTest { routeListingPreference); mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); - assertThat(mInfoMediaManager.mMediaDevices).hasSize(3); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(4); assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID); - assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4); - assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue(); - assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3); + assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_1); + assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_4); + assertThat(mInfoMediaManager.mMediaDevices.get(2).isSuggestedDevice()).isTrue(); + assertThat(mInfoMediaManager.mMediaDevices.get(3).getId()).isEqualTo(TEST_ID_3); } @Test @@ -405,8 +423,13 @@ public class InfoMediaManagerTest { when(availableInfo3.getClientPackageName()).thenReturn(packageName); availableRoutes.add(availableInfo3); - when(mRouterManager.getAvailableRoutes(packageName)).thenReturn( - availableRoutes); + final MediaRoute2Info availableInfo4 = mock(MediaRoute2Info.class); + when(availableInfo4.getId()).thenReturn(TEST_ID_1); + when(availableInfo4.isSystemRoute()).thenReturn(true); + when(availableInfo4.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + availableRoutes.add(availableInfo4); + + when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(availableRoutes); return availableRoutes; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index c058a61a3e9e..f22e090fe7df 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -19,6 +19,9 @@ import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP; import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; +import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP; + +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +35,7 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.NearbyDevice; +import android.media.RouteListingPreference; import android.os.Parcel; import com.android.settingslib.bluetooth.A2dpProfile; @@ -110,6 +114,8 @@ public class MediaDeviceTest { @Mock private MediaRouter2Manager mMediaRouter2Manager; + private RouteListingPreference.Item mItem; + private BluetoothMediaDevice mBluetoothMediaDevice1; private BluetoothMediaDevice mBluetoothMediaDevice2; private BluetoothMediaDevice mBluetoothMediaDevice3; @@ -497,4 +503,21 @@ public class MediaDeviceTest { assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0); } + + @Test + public void getSelectionBehavior_setItemWithSelectionBehaviorOnSystemRoute_returnTransfer() { + mItem = new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1) + .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP) + .build(); + mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1, + mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME, mItem); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo, + TEST_PACKAGE_NAME, mItem); + + assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 887f19b8bbb3..4cb7f94f8e6d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3560,11 +3560,11 @@ public class SettingsProvider extends ContentProvider { if (isSecureSettingsKey(key)) { maybeNotifyProfiles(getTypeFromKey(key), userId, uri, name, sSecureCloneToManagedSettings); - maybeNotifyProfiles(SETTINGS_TYPE_SYSTEM, userId, uri, name, - sSystemCloneFromParentOnDependency.values()); } else if (isSystemSettingsKey(key)) { maybeNotifyProfiles(getTypeFromKey(key), userId, uri, name, sSystemCloneToManagedSettings); + maybeNotifyProfiles(SETTINGS_TYPE_SYSTEM, userId, uri, name, + sSystemCloneFromParentOnDependency.keySet()); } } diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 1811c02d549d..64c0f99f4ba7 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -128,6 +128,16 @@ public interface BcSmartspaceDataPlugin extends Plugin { void setDozeAmount(float amount); /** + * Set if dozing is true or false + */ + default void setDozing(boolean dozing) {} + + /** + * Set if split shade enabled + */ + default void setSplitShadeEnabled(boolean enabled) {} + + /** * Set the current keyguard bypass enabled status. */ default void setKeyguardBypassEnabled(boolean enabled) {} diff --git a/packages/SystemUI/res-product/values/strings.xml b/packages/SystemUI/res-product/values/strings.xml index 13f72af084b7..42733a20ffae 100644 --- a/packages/SystemUI/res-product/values/strings.xml +++ b/packages/SystemUI/res-product/values/strings.xml @@ -122,6 +122,44 @@ Try again in <xliff:g id="number">%3$d</xliff:g> seconds. </string> + <!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] --> + <string name="thermal_shutdown_title" product="default">Phone turned off due to heat</string> + <!-- Title for notification & dialog that the user's device last shut down because it got too hot. [CHAR LIMIT=40] --> + <string name="thermal_shutdown_title" product="device">Device turned off due to heat</string> + <!-- Title for notification & dialog that the user's tablet last shut down because it got too hot. [CHAR LIMIT=40] --> + <string name="thermal_shutdown_title" product="tablet">Tablet turned off due to heat</string> + <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=120] --> + <string name="thermal_shutdown_message" product="default">Your phone is now running normally.\nTap for more info</string> + <!-- Message body for notification that user's device last shut down because it got too hot. [CHAR LIMIT=120] --> + <string name="thermal_shutdown_message" product="device">Your device is now running normally.\nTap for more info</string> + <!-- Message body for notification that user's tablet last shut down because it got too hot. [CHAR LIMIT=120] --> + <string name="thermal_shutdown_message" product="tablet">Your tablet is now running normally.\nTap for more info</string> + <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=500] --> + <string name="thermal_shutdown_dialog_message" product="default">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t• Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t• Download or upload large files\n\t• Use your phone in high temperatures</string> + <!-- Text body for dialog alerting user that their device last shut down because it got too hot. [CHAR LIMIT=500] --> + <string name="thermal_shutdown_dialog_message" product="device">Your device was too hot, so it turned off to cool down. Your device is now running normally.\n\nYour device may get too hot if you:\n\t• Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t• Download or upload large files\n\t• Use your device in high temperatures</string> + <!-- Text body for dialog alerting user that their tablet last shut down because it got too hot. [CHAR LIMIT=500] --> + <string name="thermal_shutdown_dialog_message" product="tablet">Your tablet was too hot, so it turned off to cool down. Your tablet is now running normally.\n\nYour tablet may get too hot if you:\n\t• Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t• Download or upload large files\n\t• Use your tablet in high temperatures</string> + + <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] --> + <string name="high_temp_title" product="default">Phone is getting warm</string> + <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] --> + <string name="high_temp_title" product="device">Device is getting warm</string> + <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] --> + <string name="high_temp_title" product="tablet">Tablet is getting warm</string> + <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] --> + <string name="high_temp_notif_message" product="default">Some features limited while phone cools down.\nTap for more info</string> + <!-- Message body for notification that user's device has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] --> + <string name="high_temp_notif_message" product="device">Some features limited while device cools down.\nTap for more info</string> + <!-- Message body for notification that user's tablet has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] --> + <string name="high_temp_notif_message" product="tablet">Some features limited while tablet cools down.\nTap for more info</string> + <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] --> + <string name="high_temp_dialog_message" product="default">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string> + <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] --> + <string name="high_temp_dialog_message" product="device">Your device will automatically try to cool down. You can still use your device, but it may run slower.\n\nOnce your device has cooled down, it will run normally.</string> + <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] --> + <string name="high_temp_dialog_message" product="tablet">Your tablet will automatically try to cool down. You can still use your tablet, but it may run slower.\n\nOnce your tablet has cooled down, it will run normally.</string> + <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, to locate the sensor (tablet) for accessibility (not shown on the screen). [CHAR LIMIT=NONE]--> <string name="security_settings_sfps_enroll_find_sensor_message" product="tablet">The fingerprint sensor is on the power button. It’s the flat button next to the raised volume button on the edge of the tablet.</string> <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, to locate the sensor (device) for accessibility (not shown on the screen). [CHAR LIMIT=NONE]--> diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml index 87b5a4c2fc5b..32dc4b335f7e 100644 --- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml +++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml @@ -20,7 +20,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurface"/> + <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh"/> <size android:width="@dimen/keyguard_affordance_fixed_width" android:height="@dimen/keyguard_affordance_fixed_height"/> diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml index c295cfe7a2e0..1b6247f29922 100644 --- a/packages/SystemUI/res/layout/activity_rear_display_education.xml +++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml @@ -28,7 +28,7 @@ app:cardCornerRadius="28dp" app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color"> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.reardisplay.RearDisplayEducationLottieViewWrapper android:id="@+id/rear_display_folded_animation" android:importantForAccessibility="no" android:layout_width="@dimen/rear_display_animation_width" diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml index 0e6b2812a8a9..bded0127ec84 100644 --- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml +++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml @@ -29,7 +29,7 @@ app:cardCornerRadius="28dp" app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color"> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.reardisplay.RearDisplayEducationLottieViewWrapper android:id="@+id/rear_display_folded_animation" android:importantForAccessibility="no" android:layout_width="@dimen/rear_display_animation_width_opened" diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml index efc661a6e974..50b3bec93731 100644 --- a/packages/SystemUI/res/layout/auth_biometric_contents.xml +++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml @@ -57,7 +57,7 @@ <include layout="@layout/auth_biometric_icon"/> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper android:id="@+id/biometric_icon_overlay" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml index 05ff1b1c2e6f..595cea8b9018 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml @@ -59,7 +59,7 @@ android:layout_height="wrap_content" android:layout_gravity="center"> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper android:id="@+id/biometric_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -67,7 +67,7 @@ android:contentDescription="@null" android:scaleType="fitXY" /> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper android:id="@+id/biometric_icon_overlay" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 50dcaf333ea1..bb32022a0b5f 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -57,7 +57,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_alarm" android:tint="@android:color/white" android:visibility="gone" @@ -68,7 +67,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_qs_dnd_on" android:tint="@android:color/white" android:visibility="gone" @@ -79,7 +77,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_signal_wifi_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml index 7105721aff70..21e0d2c0b8d7 100644 --- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml +++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml @@ -164,7 +164,6 @@ /> <ImageView android:id="@+id/media_output_item_end_click_icon" - android:src="@drawable/media_output_status_edit_session" android:layout_width="24dp" android:layout_height="24dp" android:focusable="false" diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml index 73050c253d72..4d952209da6f 100644 --- a/packages/SystemUI/res/layout/sidefps_view.xml +++ b/packages/SystemUI/res/layout/sidefps_view.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.airbnb.lottie.LottieAnimationView +<com.android.systemui.biometrics.SideFpsLottieViewWrapper xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/sidefps_animation" diff --git a/packages/SystemUI/res/layout/udfps_keyguard_preview.xml b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml index c068b7bc46a9..0964a21aeb36 100644 --- a/packages/SystemUI/res/layout/udfps_keyguard_preview.xml +++ b/packages/SystemUI/res/layout/udfps_keyguard_preview.xml @@ -24,7 +24,7 @@ android:background="@drawable/fingerprint_bg"> <!-- LockScreen fingerprint icon from 0 stroke width to full width --> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.keyguard.ui.view.UdfpsLottieViewWrapper android:layout_width="0dp" android:layout_height="0dp" android:scaleType="centerCrop" diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml index 191158e4e8c2..1d6147cd2169 100644 --- a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml +++ b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml @@ -32,7 +32,7 @@ <!-- Fingerprint --> <!-- AOD dashed fingerprint icon with moving dashes --> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.keyguard.ui.view.UdfpsLottieViewWrapper android:id="@+id/udfps_aod_fp" android:layout_width="match_parent" android:layout_height="match_parent" @@ -43,7 +43,7 @@ app:lottie_rawRes="@raw/udfps_aod_fp"/> <!-- LockScreen fingerprint icon from 0 stroke width to full width --> - <com.airbnb.lottie.LottieAnimationView + <com.android.systemui.keyguard.ui.view.UdfpsLottieViewWrapper android:id="@+id/udfps_lockscreen_fp" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4f768cc39b40..44a5c07502ac 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1781,7 +1781,6 @@ <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen> - <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen> <!-- Default device corner radius, used for assist UI --> <dimen name="config_rounded_mask_size">0px</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 663efea1944b..070748cb92f8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2089,23 +2089,11 @@ <!-- Tuner string --> <!-- Tuner string --> - <!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] --> - <string name="thermal_shutdown_title">Phone turned off due to heat</string> - <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=120] --> - <string name="thermal_shutdown_message">Your phone is now running normally.\nTap for more info</string> - <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=500] --> - <string name="thermal_shutdown_dialog_message">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t• Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t• Download or upload large files\n\t• Use your phone in high temperatures</string> <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] --> <string name="thermal_shutdown_dialog_help_text">See care steps</string> <!-- URL for care instructions for overheating devices --> <string name="thermal_shutdown_dialog_help_url" translatable="false"></string> - <!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] --> - <string name="high_temp_title">Phone is getting warm</string> - <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] --> - <string name="high_temp_notif_message">Some features limited while phone cools down.\nTap for more info</string> - <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] --> - <string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string> <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] --> <string name="high_temp_dialog_help_text">See care steps</string> <!-- URL for care instructions for overheating devices --> diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml index bf576dc5790b..3881e8c1c4cc 100644 --- a/packages/SystemUI/res/xml/large_screen_shade_header.xml +++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml @@ -60,7 +60,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintEnd_toEndOf="@id/carrier_group"/> + app:layout_constraintStart_toEndOf="@id/carrier_group"/> <PropertySet android:alpha="1" /> </Constraint> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt index 635f0fa44234..1cb8e43cf2c8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt @@ -13,7 +13,8 @@ class KeyguardClockFrame( private var drawAlpha: Int = 255 protected override fun onSetAlpha(alpha: Int): Boolean { - drawAlpha = alpha + // Ignore alpha passed from View, prefer to compute it from set values + drawAlpha = (255 * this.alpha * transitionAlpha).toInt() return true } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 41c1eda42e83..a0261309d61a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -352,6 +352,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean splitShadeEnabled) { + mSmartspaceController.setSplitShadeEnabled(splitShadeEnabled); + } + + /** * Set which clock should be displayed on the keyguard. The other one will be automatically * hidden. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 00500d617766..6854c97c3415 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -323,6 +323,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean enabled) { + mKeyguardClockSwitchController.setSplitShadeEnabled(enabled); + } + + /** * Updates the alignment of the KeyguardStatusView and animates the transition if requested. */ public void updateAlignment( @@ -350,6 +357,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + /* This transition blocks any layout changes while running. For that reason + * special logic with setting visibility was added to {@link BcSmartspaceView#setDozing} + * for split shade to avoid jump of the media object. */ ChangeBounds transition = new ChangeBounds(); if (splitShadeEnabled) { // Excluding media from the transition on split-shade, as it doesn't transition diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index ae061c032429..5d1a8444ae1d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -296,6 +296,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName( "com.android.settings", "com.android.settings.FallbackHome"); + private static final List<Integer> ABSENT_SIM_STATE_LIST = Arrays.asList( + TelephonyManager.SIM_STATE_ABSENT, + TelephonyManager.SIM_STATE_UNKNOWN, + TelephonyManager.SIM_STATE_NOT_READY); + private final Context mContext; private final UserTracker mUserTracker; private final KeyguardUpdateMonitorLogger mLogger; @@ -3698,8 +3703,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.logSimState(subId, slotId, state); boolean becameAbsent = false; - if (!SubscriptionManager.isValidSubscriptionId(subId) - && state != TelephonyManager.SIM_STATE_UNKNOWN) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { mLogger.w("invalid subId in handleSimStateChange()"); /* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to * handleServiceStateChange() handle other case */ @@ -3717,11 +3721,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } else if (state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) { updateTelephonyCapable(true); - } else { - return; } } + becameAbsent |= ABSENT_SIM_STATE_LIST.contains(state); + SimData data = mSimDatas.get(subId); final boolean changed; if (data == null) { @@ -3734,7 +3738,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab data.subId = subId; data.slotId = slotId; } - if ((changed || becameAbsent) || state == TelephonyManager.SIM_STATE_UNKNOWN) { + if ((changed || becameAbsent)) { for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -4477,13 +4481,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * Cancels all operations in the scheduler if it is hung for 10 seconds. */ public void startBiometricWatchdog() { - if (mFaceManager != null && !isFaceAuthInteractorEnabled()) { - mLogger.scheduleWatchdog("face"); - mFaceManager.scheduleWatchdog(); - } - if (mFpm != null) { - mLogger.scheduleWatchdog("fingerprint"); - mFpm.scheduleWatchdog(); - } + final boolean isFaceAuthInteractorEnabled = isFaceAuthInteractorEnabled(); + mBackgroundExecutor.execute(() -> { + Trace.beginSection("#startBiometricWatchdog"); + if (mFaceManager != null && !isFaceAuthInteractorEnabled) { + mLogger.scheduleWatchdog("face"); + mFaceManager.scheduleWatchdog(); + } + if (mFpm != null) { + mLogger.scheduleWatchdog("fingerprint"); + mFpm.scheduleWatchdog(); + } + Trace.endSection(); + }); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 239a0cc01c45..9003c43b5cc9 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -62,6 +62,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -112,6 +113,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final VibratorHelper mVibrator; @Nullable private final AuthRippleController mAuthRippleController; @NonNull private final FeatureFlags mFeatureFlags; + @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; @NonNull private final KeyguardTransitionInteractor mTransitionInteractor; @NonNull private final KeyguardInteractor mKeyguardInteractor; @@ -180,7 +182,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull @Main Resources resources, @NonNull KeyguardTransitionInteractor transitionInteractor, @NonNull KeyguardInteractor keyguardInteractor, - @NonNull FeatureFlags featureFlags + @NonNull FeatureFlags featureFlags, + PrimaryBouncerInteractor primaryBouncerInteractor ) { super(view); mStatusBarStateController = statusBarStateController; @@ -197,6 +200,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mTransitionInteractor = transitionInteractor; mKeyguardInteractor = keyguardInteractor; mFeatureFlags = featureFlags; + mPrimaryBouncerInteractor = primaryBouncerInteractor; mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); @@ -325,8 +329,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setContentDescription(null); } + boolean accessibilityEnabled = + !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser(); + mView.setImportantForAccessibility( + accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES + : View.IMPORTANT_FOR_ACCESSIBILITY_NO); + if (!Objects.equals(prevContentDescription, mView.getContentDescription()) - && mView.getContentDescription() != null && mView.isVisibleToUser()) { + && mView.getContentDescription() != null && accessibilityEnabled) { mView.announceForAccessibility(mView.getContentDescription()); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricPromptLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricPromptLottieViewWrapper.kt new file mode 100644 index 000000000000..e48e6e2dfdc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricPromptLottieViewWrapper.kt @@ -0,0 +1,24 @@ +/* + * 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.biometrics + +import android.content.Context +import android.util.AttributeSet +import com.android.systemui.util.wrapper.LottieViewWrapper + +class BiometricPromptLottieViewWrapper +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 7c412237650c..240390e369f8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -117,6 +117,8 @@ constructor( private var overlayView: View? = null set(value) { field?.let { oldView -> + val lottie = oldView.findViewById(R.id.sidefps_animation) as LottieAnimationView + lottie.pauseAnimation() windowManager.removeView(oldView) orientationListener.disable() } @@ -193,7 +195,9 @@ constructor( requests.add(request) mainExecutor.execute { if (overlayView == null) { - traceSection("SideFpsController#show(request=${request.name}, reason=$reason") { + traceSection( + "SideFpsController#show(request=${request.name}, reason=$reason)" + ) { createOverlayForDisplay(reason) } } else { @@ -208,7 +212,7 @@ constructor( requests.remove(request) mainExecutor.execute { if (requests.isEmpty()) { - traceSection("SideFpsController#hide(${request.name}") { overlayView = null } + traceSection("SideFpsController#hide(${request.name})") { overlayView = null } } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt new file mode 100644 index 000000000000..e98f6db12d34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt @@ -0,0 +1,24 @@ +/* + * 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.biometrics + +import android.content.Context +import android.util.AttributeSet +import com.android.systemui.util.wrapper.LottieViewWrapper + +class SideFpsLottieViewWrapper +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 2eb533029cf5..10e45dadee60 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -589,6 +589,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { // Pilfer if valid overlap, don't allow following events to reach keyguard shouldPilfer = true; + + // Touch is a valid UDFPS touch. Inform the falsing manager so that the touch + // isn't counted against the falsing algorithm as an accidental touch. + // We do this on the DOWN event instead of CANCEL/UP because the CANCEL/UP events + // get sent too late to this receiver (after the actual cancel/up motions occur), + // and therefore wouldn't end up being used as part of the falsing algo. + mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UP: @@ -608,7 +615,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { data.getTime(), data.getGestureStart(), mStatusBarStateController.isDozing()); - mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UNCHANGED: diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index d73c85b0803b..776b336e7b61 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -279,7 +279,7 @@ class ControlsUiControllerImpl @Inject constructor ( controlsListingController.get().removeCallback(listingCallback) controlsController.get().unsubscribe() - taskViewController?.dismiss() + taskViewController?.removeTask() taskViewController = null val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f) @@ -777,7 +777,7 @@ class ControlsUiControllerImpl @Inject constructor ( closeDialogs(true) controlsController.get().unsubscribe() - taskViewController?.dismiss() + taskViewController?.removeTask() taskViewController = null controlsById.clear() diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt index 025d7e40201e..db009dc46d89 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt @@ -18,7 +18,6 @@ package com.android.systemui.controls.ui import android.app.ActivityOptions -import android.app.ActivityTaskManager import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.PendingIntent import android.content.ComponentName @@ -28,6 +27,7 @@ import android.graphics.Color import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RoundRectShape import android.os.Trace +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.R import com.android.systemui.util.boundsOnScreen import com.android.wm.shell.taskview.TaskView @@ -54,12 +54,6 @@ class PanelTaskViewController( addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) } - private fun removeDetailTask() { - if (detailTaskId == INVALID_TASK_ID) return - ActivityTaskManager.getInstance().removeTask(detailTaskId) - detailTaskId = INVALID_TASK_ID - } - private val stateCallback = object : TaskView.Listener { override fun onInitialized() { @@ -95,7 +89,7 @@ class PanelTaskViewController( override fun onTaskRemovalStarted(taskId: Int) { detailTaskId = INVALID_TASK_ID - dismiss() + release() } override fun onTaskCreated(taskId: Int, name: ComponentName?) { @@ -103,12 +97,7 @@ class PanelTaskViewController( taskView.alpha = 1f } - override fun onReleased() { - removeDetailTask() - } - override fun onBackPressedOnTaskRoot(taskId: Int) { - dismiss() hide() } } @@ -117,10 +106,17 @@ class PanelTaskViewController( taskView.onLocationChanged() } - fun dismiss() { + /** Call when the taskView is no longer being used, shouldn't be called before removeTask. */ + @VisibleForTesting + fun release() { taskView.release() } + /** Call to explicitly remove the task from window manager. */ + fun removeTask() { + taskView.removeTask() + } + fun launchTaskView() { taskView.setListener(uiExecutor, stateCallback) } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 951d077808c9..366056af7ab3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -683,11 +683,11 @@ object Flags { // TODO(b/283071711): Tracking bug @JvmField val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK = - releasedFlag(2401, "trim_resources_with_background_trim_on_lock") + unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock") // TODO:(b/283203305): Tracking bug @JvmField - val TRIM_FONT_CACHES_AT_UNLOCK = releasedFlag(2402, "trim_font_caches_on_unlock") + val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag(2402, "trim_font_caches_on_unlock") // 2700 - unfold transitions // TODO(b/265764985): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index bc41ab3156df..1a06b0184bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -99,6 +99,7 @@ import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -139,6 +140,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -166,6 +168,8 @@ import com.android.wm.shell.keyguard.KeyguardTransitions; import dagger.Lazy; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; @@ -250,6 +254,22 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private static final int SYSTEM_READY = 18; private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19; + /** Enum for reasons behind updating wakeAndUnlock state. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + value = { + WakeAndUnlockUpdateReason.HIDE, + WakeAndUnlockUpdateReason.SHOW, + WakeAndUnlockUpdateReason.FULFILL, + WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK, + }) + @interface WakeAndUnlockUpdateReason { + int HIDE = 0; + int SHOW = 1; + int FULFILL = 2; + int WAKE_AND_UNLOCK = 3; + } + /** * The default amount of time we stay awake (used for all key input) */ @@ -432,6 +452,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final LockPatternUtils mLockPatternUtils; private final BroadcastDispatcher mBroadcastDispatcher; private boolean mKeyguardDonePending = false; + private boolean mUnlockingAndWakingFromDream = false; private boolean mHideAnimationRun = false; private boolean mHideAnimationRunning = false; @@ -538,6 +559,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; + private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -642,6 +665,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, switch (simState) { case TelephonyManager.SIM_STATE_NOT_READY: case TelephonyManager.SIM_STATE_ABSENT: + case TelephonyManager.SIM_STATE_UNKNOWN: + mPendingPinLock = false; // only force lock screen in case of missing sim if user hasn't // gone through setup wizard synchronized (KeyguardViewMediator.this) { @@ -706,9 +731,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } break; - case TelephonyManager.SIM_STATE_UNKNOWN: - mPendingPinLock = false; - break; default: if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState); break; @@ -804,6 +826,25 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false); mKeyguardDisplayManager.hide(); mUpdateMonitor.startBiometricWatchdog(); + + // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while + // dreaming. It's time to wake up. + if (mUnlockingAndWakingFromDream) { + Log.d(TAG, "waking from dream after unlock"); + setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.FULFILL); + + if (mKeyguardStateController.isShowing()) { + Log.d(TAG, "keyguard showing after keyguardGone, dismiss"); + mKeyguardViewControllerLazy.get() + .notifyKeyguardAuthenticated(!mWakeAndUnlocking); + } else { + Log.d(TAG, "keyguard gone, waking up from dream"); + mPM.wakeUp(SystemClock.uptimeMillis(), + mWakeAndUnlocking ? PowerManager.WAKE_REASON_BIOMETRIC + : PowerManager.WAKE_REASON_GESTURE, + "com.android.systemui:UNLOCK_DREAMING"); + } + } Trace.endSection(); } @@ -1165,6 +1206,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, getRemoteSurfaceAlphaApplier().accept(0.0f); mDreamingToLockscreenTransitionViewModel.get() .startTransition(); + mUnoccludeFromDreamFinishedCallback = finishedCallback; return; } @@ -1244,6 +1286,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, }; } + private Consumer<TransitionStep> getFinishedCallbackConsumer() { + return (TransitionStep step) -> { + if (mUnoccludeFromDreamFinishedCallback == null) return; + try { + mUnoccludeFromDreamFinishedCallback.onAnimationFinished(); + mUnoccludeFromDreamFinishedCallback = null; + } catch (RemoteException e) { + Log.e(TAG, "Wasn't able to callback", e); + } + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); + }; + } + private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; @@ -1503,6 +1558,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, collectFlow(viewRootImpl.getView(), mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(), getRemoteSurfaceAlphaApplier(), mMainDispatcher); + collectFlow(viewRootImpl.getView(), + mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(), + getFinishedCallbackConsumer(), mMainDispatcher); } } // Most services aren't available until the system reaches the ready state, so we @@ -2615,6 +2673,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardExitAnimationRunner = null; mWakeAndUnlocking = false; + setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.SHOW); setPendingLock(false); // Force if we we're showing in the middle of hiding, to ensure we end up in the correct @@ -2720,6 +2779,51 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, tryKeyguardDone(); }; + private void setUnlockAndWakeFromDream(boolean updatedValue, + @WakeAndUnlockUpdateReason int reason) { + if (updatedValue == mUnlockingAndWakingFromDream) { + return; + } + + final String reasonDescription; + + switch(reason) { + case WakeAndUnlockUpdateReason.FULFILL: + reasonDescription = "fulfilling existing request"; + break; + case WakeAndUnlockUpdateReason.HIDE: + reasonDescription = "hiding keyguard"; + break; + case WakeAndUnlockUpdateReason.SHOW: + reasonDescription = "showing keyguard"; + break; + case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK: + reasonDescription = "waking to unlock"; + break; + default: + throw new IllegalStateException("Unexpected value: " + reason); + } + + final boolean unsetUnfulfilled = !updatedValue + && reason != WakeAndUnlockUpdateReason.FULFILL; + + mUnlockingAndWakingFromDream = updatedValue; + + final String description; + + if (unsetUnfulfilled) { + description = "Interrupting request to wake and unlock"; + } else if (mUnlockingAndWakingFromDream) { + description = "Initiating request to wake and unlock"; + } else { + description = "Fulfilling request to wake and unlock"; + } + + Log.d(TAG, String.format( + "Updating waking and unlocking request to %b. description:[%s]. reason:[%s]", + mUnlockingAndWakingFromDream, description, reasonDescription)); + } + /** * Handle message sent by {@link #hideLocked()} * @see #HIDE @@ -2739,7 +2843,16 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mHiding = true; - if (mShowing && !mOccluded) { + // If waking and unlocking, waking from dream has been set properly. + if (!mWakeAndUnlocking) { + setUnlockAndWakeFromDream(mStatusBarStateController.isDreaming() + && mPM.isInteractive(), WakeAndUnlockUpdateReason.HIDE); + } + + if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) { + if (mUnlockingAndWakingFromDream) { + Log.d(TAG, "hiding keyguard before waking from dream"); + } mKeyguardGoingAwayRunnable.run(); } else { // TODO(bc-unlock): Fill parameters @@ -2750,13 +2863,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, null /* nonApps */, null /* finishedCallback */); }); } - - // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while - // dreaming. It's time to wake up. - if (mDreamOverlayShowing) { - mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, - "com.android.systemui:UNLOCK_DREAMING"); - } } Trace.endSection(); } @@ -2926,6 +3032,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } private void onKeyguardExitFinished() { + if (DEBUG) Log.d(TAG, "onKeyguardExitFinished()"); // only play "unlock" noises if not on a call (since the incall UI // disables the keyguard) if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { @@ -3147,13 +3254,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, flags |= StatusBarManager.DISABLE_RECENT; } - if (mPowerGestureIntercepted) { + if (mPowerGestureIntercepted && mOccluded && isSecure()) { flags |= StatusBarManager.DISABLE_RECENT; } if (DEBUG) { Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons + + " mPowerGestureIntercepted=" + mPowerGestureIntercepted + " --> flags=0x" + Integer.toHexString(flags)); } @@ -3241,9 +3349,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } - public void onWakeAndUnlocking() { + /** + * Informs the keyguard view mediator that the device is waking and unlocking. + * @param fromDream Whether waking and unlocking is happening over an interactive dream. + */ + public void onWakeAndUnlocking(boolean fromDream) { Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); mWakeAndUnlocking = true; + setUnlockAndWakeFromDream(fromDream, WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK); mKeyguardViewControllerLazy.get().notifyKeyguardAuthenticated(/* primaryAuth */ false); userActivity(); @@ -3381,6 +3494,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, pw.print(" mPendingLock: "); pw.println(mPendingLock); pw.print(" wakeAndUnlocking: "); pw.println(mWakeAndUnlocking); pw.print(" mPendingPinLock: "); pw.println(mPendingPinLock); + pw.print(" mPowerGestureIntercepted: "); pw.println(mPowerGestureIntercepted); } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 9621f03f63a0..2f506bb6a18e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -30,6 +30,7 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags @@ -126,6 +127,7 @@ constructor( private val keyguardBypassController: KeyguardBypassController? = null, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, + @Background private val backgroundDispatcher: CoroutineDispatcher, private val sessionTracker: SessionTracker, private val uiEventsLogger: UiEventLogger, private val faceAuthLogger: FaceAuthenticationLogger, @@ -228,8 +230,12 @@ constructor( keyguardTransitionInteractor.anyStateToGoneTransition .filter { it.transitionState == TransitionState.FINISHED } .onEach { - faceAuthLogger.watchdogScheduled() - faceManager?.scheduleWatchdog() + // We deliberately want to run this in background because scheduleWatchdog does + // a Binder IPC. + withContext(backgroundDispatcher) { + faceAuthLogger.watchdogScheduled() + faceManager?.scheduleWatchdog() + } } .launchIn(applicationScope) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 42f12f82d9a7..41a81a83d06d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -49,11 +49,15 @@ constructor( ) { /** (any)->GONE transition information */ val anyStateToGoneTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.GONE } + repository.transitions.filter { step -> step.to == GONE } /** (any)->AOD transition information */ val anyStateToAodTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.AOD } + repository.transitions.filter { step -> step.to == AOD } + + /** DREAMING->(any) transition information. */ + val fromDreamingTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.from == DREAMING } /** AOD->LOCKSCREEN transition information. */ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index a8d662c96284..5ad6e5196f66 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -377,9 +377,9 @@ object KeyguardBottomAreaViewBinder { Utils.getColorAttrDefaultColor( view.context, if (viewModel.isActivated) { - com.android.internal.R.attr.textColorPrimaryInverse + com.android.internal.R.attr.materialColorOnPrimaryFixed } else { - com.android.internal.R.attr.textColorPrimary + com.android.internal.R.attr.materialColorOnSurface }, ) ) @@ -389,9 +389,9 @@ object KeyguardBottomAreaViewBinder { Utils.getColorAttr( view.context, if (viewModel.isActivated) { - com.android.internal.R.attr.colorAccentPrimary + com.android.internal.R.attr.materialColorPrimaryFixed } else { - com.android.internal.R.attr.colorSurface + com.android.internal.R.attr.materialColorSurfaceContainerHigh } ) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 1c2e85b0fd3a..b92d10474ccd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -346,6 +346,10 @@ constructor( ?.largeClock ?.events ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView)) + clockController.clock + ?.smallClock + ?.events + ?.onTargetRegionChanged(KeyguardClockSwitch.getSmallClockRegion(parentView)) } } parentView.addOnLayoutChangeListener(layoutChangeListener) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/UdfpsLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/UdfpsLottieViewWrapper.kt new file mode 100644 index 000000000000..3a2c3c70a452 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/UdfpsLottieViewWrapper.kt @@ -0,0 +1,24 @@ +/* + * 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.ui.view + +import android.content.Context +import android.util.AttributeSet +import com.android.systemui.util.wrapper.LottieViewWrapper + +class UdfpsLottieViewWrapper +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index 9ca4bd62b6fe..e24d326850e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -48,7 +48,7 @@ constructor( ) val transitionEnded = - keyguardTransitionInteractor.dreamingToLockscreenTransition.filter { step -> + keyguardTransitionInteractor.fromDreamingTransition.filter { step -> step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt index b0389b50cd7d..23ee00d88fdc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt @@ -122,9 +122,9 @@ constructor( Log.e(TAG, "Error getting package information", e) } - Log.d(TAG, "Adding resume controls $desc") + Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc") mediaDataManager.addResumptionControls( - currentUserId, + browser.userId, desc, resumeAction, token, @@ -196,7 +196,11 @@ constructor( } resumeComponents.add(component to lastPlayed) } - Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}") + Log.d( + TAG, + "loaded resume components for $currentUserId: " + + "${resumeComponents.toArray().contentToString()}" + ) if (needsUpdate) { // Save any missing times that we had to fill in @@ -210,11 +214,21 @@ constructor( return } + val pm = context.packageManager val now = systemClock.currentTimeMillis() resumeComponents.forEach { if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) { - val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first) - browser.findRecentMedia() + // Verify that the service exists for this user + val intent = Intent(MediaBrowserService.SERVICE_INTERFACE) + intent.component = it.first + val inf = pm.resolveServiceAsUser(intent, 0, currentUserId) + if (inf != null) { + val browser = + mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId) + browser.findRecentMedia() + } else { + Log.d(TAG, "User $currentUserId does not have component ${it.first}") + } } } } @@ -244,7 +258,7 @@ constructor( Log.d(TAG, "Checking for service component for " + data.packageName) val pm = context.packageManager val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) - val resumeInfo = pm.queryIntentServices(serviceIntent, 0) + val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId) val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName } if (inf != null && inf.size > 0) { @@ -280,13 +294,17 @@ constructor( browser: ResumeMediaBrowser ) { // Since this is a test, just save the component for later - Log.d(TAG, "Can get resumable media from $componentName") + Log.d( + TAG, + "Can get resumable media for ${browser.userId} from $componentName" + ) mediaDataManager.setResumeAction(key, getResumeAction(componentName)) updateResumptionList(componentName) mediaBrowser = null } }, - componentName + componentName, + currentUserId ) mediaBrowser?.testConnection() } @@ -326,7 +344,7 @@ constructor( /** Get a runnable which will resume media playback */ private fun getResumeAction(componentName: ComponentName): Runnable { return Runnable { - mediaBrowser = mediaBrowserFactory.create(null, componentName) + mediaBrowser = mediaBrowserFactory.create(null, componentName, currentUserId) mediaBrowser?.restart() } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java index d460b5b5d782..ceaccafd8f40 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.resume; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -53,6 +54,7 @@ public class ResumeMediaBrowser { private final ResumeMediaBrowserLogger mLogger; private final ComponentName mComponentName; private final MediaController.Callback mMediaControllerCallback = new SessionDestroyCallback(); + @UserIdInt private final int mUserId; private MediaBrowser mMediaBrowser; @Nullable private MediaController mMediaController; @@ -62,18 +64,21 @@ public class ResumeMediaBrowser { * @param context the context * @param callback used to report media items found * @param componentName Component name of the MediaBrowserService this browser will connect to + * @param userId ID of the current user */ public ResumeMediaBrowser( Context context, @Nullable Callback callback, ComponentName componentName, MediaBrowserFactory browserFactory, - ResumeMediaBrowserLogger logger) { + ResumeMediaBrowserLogger logger, + @UserIdInt int userId) { mContext = context; mCallback = callback; mComponentName = componentName; mBrowserFactory = browserFactory; mLogger = logger; + mUserId = userId; } /** @@ -285,6 +290,14 @@ public class ResumeMediaBrowser { } /** + * Get the ID of the user associated with this broswer + * @return the user ID + */ + public @UserIdInt int getUserId() { + return mUserId; + } + + /** * Get the media session token * @return the token, or null if the MediaBrowser is null or disconnected */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java index c558227df0b5..e37419127f5b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java @@ -16,6 +16,7 @@ package com.android.systemui.media.controls.resume; +import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; @@ -42,10 +43,12 @@ public class ResumeMediaBrowserFactory { * * @param callback will be called on connection or error, and addTrack when media item found * @param componentName component to browse + * @param userId ID of the current user * @return */ public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback, - ComponentName componentName) { - return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger); + ComponentName componentName, @UserIdInt int userId) { + return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory, mLogger, + userId); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 30ee147e302a..2883210805d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -128,6 +128,15 @@ constructor( var visibilityChangedListener: ((Boolean) -> Unit)? = null + /** + * Whether the doze wake up animation is delayed and we are currently waiting for it to start. + */ + var isDozeWakeUpAnimationWaiting: Boolean = false + set(value) { + field = value + refreshMediaPosition() + } + /** single pane media container placed at the top of the notifications list */ var singlePaneContainer: MediaContainerView? = null private set @@ -221,7 +230,13 @@ constructor( // by the clock. This is not the case for single-line clock though. // For single shade, we don't need to do it, because media is a child of NSSL, which already // gets hidden on AOD. - return !statusBarStateController.isDozing + // Media also has to be hidden when waking up from dozing, and the doze wake up animation is + // delayed and waiting to be started. + // This is to stay in sync with the delaying of the horizontal alignment of the rest of the + // keyguard container, that is also delayed until the "wait" is over. + // If we show media during this waiting period, the shade will still be centered, and using + // the entire width of the screen, and making media show fully stretched. + return !statusBarStateController.isDozing && !isDozeWakeUpAnimationWaiting } private fun showMediaPlayer() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 318cd99a06ed..26a7d048cf27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -20,6 +20,7 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.DrawableRes; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.AnimatedVectorDrawable; @@ -181,27 +182,23 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mController.getSelectedMediaDevice(), device))); boolean isHost = device.isHostForOngoingSession() && isActiveWithOngoingSession; - if (isHost) { + if (isActiveWithOngoingSession) { mCurrentActivePosition = position; updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); mSubTitleText.setText(device.getSubtextString()); updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA); - updateEndClickAreaAsSessionEditing(device); + updateEndClickAreaAsSessionEditing(device, + isHost ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); setTwoLineLayout(device, null /* title */, true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, false /* showStatus */, true /* showEndTouchArea */, false /* isFakeActive */); initSeekbar(device, isCurrentSeekbarInvisible); } else { - if (isActiveWithOngoingSession) { - //Selected device which has ongoing session, disable seekbar since we - //only allow volume control on Host + if (currentlyConnected) { mCurrentActivePosition = position; - } - boolean showSeekbar = - (!device.hasOngoingSession() && currentlyConnected); - if (showSeekbar) { updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); initSeekbar(device, isCurrentSeekbarInvisible); @@ -222,10 +219,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateClickActionBasedOnSelectionBehavior(device) ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); setTwoLineLayout(device, currentlyConnected /* bFocused */, - showSeekbar /* showSeekBar */, + currentlyConnected /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, deviceStatusIcon != null /* showStatus */, - isActiveWithOngoingSession /* isFakeActive */); + false /* isFakeActive */); } } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); @@ -267,25 +264,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device)); } else if (device.hasOngoingSession()) { mCurrentActivePosition = position; - if (device.isHostForOngoingSession()) { - updateTitleIcon(R.drawable.media_output_icon_volume, - mController.getColorItemContent()); - updateEndClickAreaAsSessionEditing(device); - mEndClickIcon.setVisibility(View.VISIBLE); - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, - false /* showProgressBar */, false /* showCheckBox */, - true /* showEndTouchArea */); - initSeekbar(device, isCurrentSeekbarInvisible); - } else { - updateDeviceStatusIcon(mContext.getDrawable( - R.drawable.ic_sound_bars_anim)); - mStatusIcon.setVisibility(View.VISIBLE); - updateSingleLineLayoutContentAlpha( - updateClickActionBasedOnSelectionBehavior(device) - ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); - setSingleLineLayout(getItemTitle(device)); - initFakeActiveDevice(); - } + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + updateEndClickAreaAsSessionEditing(device, device.isHostForOngoingSession() + ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); + mEndClickIcon.setVisibility(View.VISIBLE); + setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + false /* showProgressBar */, false /* showCheckBox */, + true /* showEndTouchArea */); + initSeekbar(device, isCurrentSeekbarInvisible); } else if (mController.isCurrentConnectedDeviceRemote() && !mController.getSelectableMediaDevice().isEmpty()) { //If device is connected and there's other selectable devices, layout as @@ -362,7 +350,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mStatusIcon.setAlpha(alphaValue); } - private void updateEndClickAreaAsSessionEditing(MediaDevice device) { + private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) { mEndClickIcon.setOnClickListener(null); mEndTouchArea.setOnClickListener(null); updateEndClickAreaColor(mController.getColorSeekbarProgress()); @@ -371,6 +359,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mEndClickIcon.setOnClickListener( v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v)); mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick()); + Drawable drawable = mContext.getDrawable(id); + mEndClickIcon.setImageDrawable(drawable); + if (drawable instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) drawable).start(); + } } public void updateEndClickAreaColor(int color) { diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayEducationLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayEducationLottieViewWrapper.kt new file mode 100644 index 000000000000..716a4d649665 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayEducationLottieViewWrapper.kt @@ -0,0 +1,24 @@ +/* + * 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.reardisplay + +import android.content.Context +import android.util.AttributeSet +import com.android.systemui.util.wrapper.LottieViewWrapper + +class RearDisplayEducationLottieViewWrapper +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 776a90d75a75..267b147fa09a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1172,6 +1172,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewComponentFactory.build(keyguardStatusView); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); updateClockAppearance(); if (mKeyguardUserSwitcherController != null) { @@ -1224,6 +1225,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onSplitShadeEnabledChanged() { mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); @@ -1619,6 +1621,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mWillPlayDelayedDozeAmountAnimation = willPlay; mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay); + mKeyguardMediaController.setDozeWakeUpAnimationWaiting(willPlay); // Once changing this value, see if we should move the clock. positionClockAndNotifications(); @@ -3560,6 +3563,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { + mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false); mTrackingPointer = -1; mAmbientState.setSwipingUp(false); if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop @@ -3581,15 +3585,19 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { if (onKeyguard) { expand = true; + mShadeLog.logEndMotionEvent("endMotionEvent: cancel while on keyguard", + forceCancel, expand); } else if (mCentralSurfaces.isBouncerShowingOverDream()) { expand = false; } else { // If we get a cancel, put the shade back to the state it was in when the // gesture started expand = !mPanelClosedOnDown; + mShadeLog.logEndMotionEvent("endMotionEvent: cancel", forceCancel, expand); } } else { expand = flingExpands(vel, vectorVel, x, y); + mShadeLog.logEndMotionEvent("endMotionEvent: flingExpands", forceCancel, expand); } mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold, @@ -4685,6 +4693,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mTouchSlopExceeded = mTouchSlopExceededBeforeDown; mMotionAborted = false; mPanelClosedOnDown = isFullyCollapsed(); + mShadeLog.logPanelClosedOnDown("intercept down touch", mPanelClosedOnDown, + mExpandedFraction); mCollapsedAndHeadsUpOnDown = false; mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; @@ -4898,6 +4908,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mMinExpandHeight = 0.0f; mPanelClosedOnDown = isFullyCollapsed(); + mShadeLog.logPanelClosedOnDown("handle down touch", mPanelClosedOnDown, + mExpandedFraction); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mMotionAborted = false; diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 2da8d5f4d921..4fdd6e19cc4c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -90,7 +90,7 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { double1 = event.y.toDouble() }, { - "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" } ) } @@ -280,6 +280,42 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { ) } + fun logEndMotionEvent( + msg: String, + forceCancel: Boolean, + expand: Boolean, + ) + { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = msg + bool1 = forceCancel + bool2 = expand + }, + { "$str1; force=$bool1; expand=$bool2" } + ) + } + + fun logPanelClosedOnDown( + msg: String, + panelClosedOnDown: Boolean, + expandFraction: Float, + ) + { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = msg + bool1 = panelClosedOnDown + double1 = expandFraction.toDouble() + }, + { "$str1; mPanelClosedOnDown=$bool1; mExpandedFraction=$double1" } + ) + } + fun flingQs(flingType: Int, isClick: Boolean) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 06f43f1eeaa5..906c5ed0fb89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -534,19 +534,7 @@ public final class KeyboardShortcutListSearch { new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_access_google_assistant), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))), - /* Lock screen: Meta + L */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_lock_screen), - Arrays.asList( - Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))), - /* Pull up Notes app for quick memo: Meta + Ctrl + N */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_quick_memo), - Arrays.asList( - Pair.create( - KeyEvent.KEYCODE_N, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))) + Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))) ); for (ShortcutKeyGroupMultiMappingInfo info : infoList) { systemGroup.addItem(info.getShortcutMultiMappingInfo()); @@ -588,21 +576,12 @@ public final class KeyboardShortcutListSearch { new ArrayList<>()); // System multitasking shortcuts: - // Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow - // Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow // Switch from Split screen to full screen: Meta + Ctrl + Up arrow - // During Split screen: replace an app from one to another: Meta + Ctrl + Down arrow String[] shortcutLabels = { - context.getString(R.string.system_multitasking_rhs), - context.getString(R.string.system_multitasking_lhs), context.getString(R.string.system_multitasking_full_screen), - context.getString(R.string.system_multitasking_replace) }; int[] keyCodes = { - KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_DOWN }; for (int i = 0; i < shortcutLabels.length; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index fb88a96c38c2..763400b307fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -27,10 +27,12 @@ import android.app.Notification; import android.app.WallpaperManager; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.hardware.display.DisplayManager; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; @@ -41,6 +43,7 @@ import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; +import android.view.Display; import android.view.View; import android.widget.ImageView; @@ -74,11 +77,15 @@ import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import dagger.Lazy; @@ -138,6 +145,14 @@ public class NotificationMediaManager implements Dumpable { private BackDropView mBackdrop; private ImageView mBackdropFront; private ImageView mBackdropBack; + private final Point mTmpDisplaySize = new Point(); + + private final DisplayManager mDisplayManager; + @Nullable + private List<String> mSmallerInternalDisplayUids; + private Display mCurrentDisplay; + + private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable; private final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override @@ -184,7 +199,8 @@ public class NotificationMediaManager implements Dumpable { SysuiColorExtractor colorExtractor, KeyguardStateController keyguardStateController, DumpManager dumpManager, - WallpaperManager wallpaperManager) { + WallpaperManager wallpaperManager, + DisplayManager displayManager) { mContext = context; mMediaArtworkProcessor = mediaArtworkProcessor; mKeyguardBypassController = keyguardBypassController; @@ -200,6 +216,7 @@ public class NotificationMediaManager implements Dumpable { mStatusBarStateController = statusBarStateController; mColorExtractor = colorExtractor; mKeyguardStateController = keyguardStateController; + mDisplayManager = displayManager; mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled(); setupNotifPipeline(); @@ -477,6 +494,48 @@ public class NotificationMediaManager implements Dumpable { } /** + * Notify lockscreen wallpaper drawable the current internal display. + */ + public void onDisplayUpdated(Display display) { + Trace.beginSection("NotificationMediaManager#onDisplayUpdated"); + mCurrentDisplay = display; + if (mWallapperDrawable != null) { + mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays()); + } + Trace.endSection(); + } + + private boolean isOnSmallerInternalDisplays() { + if (mSmallerInternalDisplayUids == null) { + mSmallerInternalDisplayUids = findSmallerInternalDisplayUids(); + } + return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId()); + } + + private List<String> findSmallerInternalDisplayUids() { + if (mSmallerInternalDisplayUids != null) { + return mSmallerInternalDisplayUids; + } + List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays( + DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) + .filter(display -> display.getType() == Display.TYPE_INTERNAL) + .collect(Collectors.toList()); + if (internalDisplays.isEmpty()) { + return List.of(); + } + Display largestDisplay = internalDisplays.stream() + .max(Comparator.comparingInt(this::getRealDisplayArea)) + .orElse(internalDisplays.get(0)); + internalDisplays.remove(largestDisplay); + return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList()); + } + + private int getRealDisplayArea(Display display) { + display.getRealSize(mTmpDisplaySize); + return mTmpDisplaySize.x * mTmpDisplaySize.y; + } + + /** * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. */ public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { @@ -551,7 +610,7 @@ public class NotificationMediaManager implements Dumpable { mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; if (lockWallpaper != null) { artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( - mBackdropBack.getResources(), lockWallpaper); + mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays()); // We're in the SHADE mode on the SIM screen - yet we still need to show // the lockscreen wallpaper in that mode. allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; @@ -611,6 +670,10 @@ public class NotificationMediaManager implements Dumpable { mBackdropBack.setBackgroundColor(0xFFFFFFFF); mBackdropBack.setImageDrawable(new ColorDrawable(c)); } else { + if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) { + mWallapperDrawable = + (LockscreenWallpaper.WallpaperDrawable) artworkDrawable; + } mBackdropBack.setImageDrawable(artworkDrawable); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index f6c9a5cae34a..c67e81f1c5c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.dagger; import android.app.IActivityManager; import android.app.WallpaperManager; import android.content.Context; +import android.hardware.display.DisplayManager; import android.os.RemoteException; import android.service.dreams.IDreamManager; import android.util.Log; @@ -144,7 +145,8 @@ public interface CentralSurfacesDependenciesModule { SysuiColorExtractor colorExtractor, KeyguardStateController keyguardStateController, DumpManager dumpManager, - WallpaperManager wallpaperManager) { + WallpaperManager wallpaperManager, + DisplayManager displayManager) { return new NotificationMediaManager( context, centralSurfacesOptionalLazy, @@ -160,7 +162,8 @@ public interface CentralSurfacesDependenciesModule { colorExtractor, keyguardStateController, dumpManager, - wallpaperManager); + wallpaperManager, + displayManager); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 518825cea5e0..6c1dc8c0a51d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -124,6 +124,7 @@ constructor( private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null + private var mSplitShadeEnabled = false // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to // how we test color updates when theme changes (See testThemeChangeUpdatesTextColor). @@ -131,6 +132,7 @@ constructor( // TODO: Move logic into SmartspaceView var stateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { + (v as SmartspaceView).setSplitShadeEnabled(mSplitShadeEnabled) smartspaceViews.add(v as SmartspaceView) connectSession() @@ -216,6 +218,11 @@ constructor( execution.assertIsMainThread() smartspaceViews.forEach { it.setDozeAmount(eased) } } + + override fun onDozingChanged(isDozing: Boolean) { + execution.assertIsMainThread() + smartspaceViews.forEach { it.setDozing(isDozing) } + } } private val deviceProvisionedListener = @@ -421,6 +428,11 @@ constructor( reloadSmartspace() } + fun setSplitShadeEnabled(enabled: Boolean) { + mSplitShadeEnabled = enabled + smartspaceViews.forEach { it.setSplitShadeEnabled(enabled) } + } + /** * Requests the smartspace session for an update. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 8aeefeeac211..d2c6d4baf817 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -840,6 +840,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { && !hasFlag(entry, Notification.FLAG_ONGOING_EVENT) && !hasFlag(entry, Notification.FLAG_BUBBLE) && !hasFlag(entry, Notification.FLAG_NO_CLEAR) + && (entry.getChannel() == null || !entry.getChannel().isImportantConversation()) && entry.getDismissState() != DISMISSED; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 62a0d138fd05..5c2f9a8d28ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -39,7 +39,10 @@ class StackCoordinator @Inject internal constructor( override fun attach(pipeline: NotifPipeline) { pipeline.addOnAfterRenderListListener(::onAfterRenderList) - groupExpansionManagerImpl.attach(pipeline) + // TODO(b/282865576): This has an issue where it makes changes to some groups without + // notifying listeners. To be fixed in QPR, but for now let's comment it out to avoid the + // group expansion bug. + // groupExpansionManagerImpl.attach(pipeline) } fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = 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 9272c376d4fe..11ba753643ac 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 @@ -411,7 +411,8 @@ public class NotificationStackScrollLayoutController { } }; - private final NotificationSwipeHelper.NotificationCallback mNotificationCallback = + @VisibleForTesting + final NotificationSwipeHelper.NotificationCallback mNotificationCallback = new NotificationSwipeHelper.NotificationCallback() { @Override @@ -470,10 +471,11 @@ public class NotificationStackScrollLayoutController { */ public void handleChildViewDismissed(View view) { + // The View needs to clean up the Swipe states, e.g. roundness. + mView.onSwipeEnd(); if (mView.getClearAllInProgress()) { return; } - mView.onSwipeEnd(); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (row.isHeadsUp()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 2d8f371aadac..ed11711f66c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -462,7 +462,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.endSection(); }; - if (mMode != MODE_NONE) { + final boolean wakingFromDream = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM + && mPowerManager.isInteractive(); + + if (mMode != MODE_NONE && !wakingFromDream) { wakeUp.run(); } switch (mMode) { @@ -498,7 +501,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp // later to awaken. } mNotificationShadeWindowController.setNotificationShadeFocusable(false); - mKeyguardViewMediator.onWakeAndUnlocking(); + mKeyguardViewMediator.onWakeAndUnlocking(wakingFromDream); Trace.endSection(); break; case MODE_ONLY_WAKE: 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 648ece527bef..ae38105e9110 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2267,6 +2267,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { void updateDisplaySize() { mDisplay.getMetrics(mDisplayMetrics); mDisplay.getSize(mCurrentDisplaySize); + mMediaManager.onDisplayUpdated(mDisplay); if (DEBUG_GESTURES) { mGestureRec.tag("display", String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt index f7426451fa50..a61914a70f59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt @@ -209,7 +209,7 @@ constructor( if (this.contains(other) || other.contains(this)) { return false } - return this.intersect(other) + return this.intersects(other.left, other.top, other.right, other.bottom) } override fun dump(pw: PrintWriter, args: Array<out String>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index c07b5e062d70..4569099eebb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -281,19 +281,25 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen /** * Drawable that aligns left horizontally and center vertically (like ImageWallpaper). + * + * <p>Aligns to the center when showing on the smaller internal display of a multi display + * device. */ public static class WallpaperDrawable extends DrawableWrapper { private final ConstantState mState; private final Rect mTmpRect = new Rect(); + private boolean mIsOnSmallerInternalDisplays; - public WallpaperDrawable(Resources r, Bitmap b) { - this(r, new ConstantState(b)); + public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) { + this(r, new ConstantState(b), isOnSmallerInternalDisplays); } - private WallpaperDrawable(Resources r, ConstantState state) { + private WallpaperDrawable(Resources r, ConstantState state, + boolean isOnSmallerInternalDisplays) { super(new BitmapDrawable(r, state.mBackground)); mState = state; + mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; } @Override @@ -332,10 +338,17 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } dy = (vheight - dheight * scale) * 0.5f; + int offsetX = 0; + // Offset to show the center area of the wallpaper on a smaller display for multi + // display device + if (mIsOnSmallerInternalDisplays) { + offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2); + } + mTmpRect.set( - bounds.left, + bounds.left + offsetX, bounds.top + Math.round(dy), - bounds.left + Math.round(dwidth * scale), + bounds.left + Math.round(dwidth * scale) + offsetX, bounds.top + Math.round(dheight * scale + dy)); super.onBoundsChange(mTmpRect); @@ -346,6 +359,17 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen return mState; } + /** + * Update bounds when the hosting display or the display size has changed. + * + * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal + * displays with the smaller area. + */ + public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) { + mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; + onBoundsChange(getBounds()); + } + static class ConstantState extends Drawable.ConstantState { private final Bitmap mBackground; @@ -361,7 +385,7 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen @Override public Drawable newDrawable(@Nullable Resources res) { - return new WallpaperDrawable(res, this); + return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 1bf63be3c8b0..d97e64ee6672 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -51,6 +51,7 @@ import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.dagger.SysUISingleton; @@ -305,6 +306,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Nullable private TaskbarDelegate mTaskbarDelegate; private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onTrustGrantedForCurrentUser( + boolean dismissKeyguard, + boolean newlyUnlocked, + @NonNull TrustGrantFlags flags, + @Nullable String message + ) { + updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide()); + } + @Override public void onEmergencyCallAction() { // Since we won't get a setOccluded call we have to reset the view manually such that @@ -430,7 +441,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mDockManager.addListener(mDockEventListener); mIsDocked = mDockManager.isDocked(); } - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); } /** Register a callback, to be invoked by the Predictive Back system. */ @@ -1564,14 +1574,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb || mode == KeyguardSecurityModel.SecurityMode.SimPuk; } - private KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide()); - } - }; - /** * Delegate used to send show and hide events to an alternate authentication method instead of * the regular pin/pattern/password bouncer. 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 592d1aa8f43f..135307accd13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -182,7 +182,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // Cancel any existing CUJs before starting the animation interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) - + PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.ALPHA) PropertyAnimator.setProperty( keyguardView, AnimatableProperty.ALPHA, 1f, AnimationProperties() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index a47f95d69c65..74352d29cd9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -57,7 +57,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -205,9 +204,6 @@ constructor( } .stateIn(scope, SharingStarted.WhileSubscribed(), null) - private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> = - MutableSharedFlow(extraBufferCapacity = 1) - override val defaultDataSubId: StateFlow<Int> = broadcastDispatcher .broadcastFlow( @@ -223,7 +219,6 @@ constructor( initialValue = INVALID_SUBSCRIPTION_ID, ) .onStart { emit(subscriptionManagerProxy.getDefaultDataSubscriptionId()) } - .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) } .stateIn(scope, SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID) private val carrierConfigChangedEvent = @@ -232,7 +227,7 @@ constructor( .onEach { logger.logActionCarrierConfigChanged() } override val defaultDataSubRatConfig: StateFlow<Config> = - merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent) + merge(defaultDataSubId, carrierConfigChangedEvent) .onStart { emit(Unit) } .mapLatest { Config.readConfig(context) } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt index 22b4c9d81d25..736b14574da0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy +import android.app.ActivityOptions import android.app.Notification import android.app.PendingIntent import android.app.RemoteInput @@ -275,7 +276,10 @@ class RemoteInputViewControllerImpl @Inject constructor( entry.sbn.instanceId) try { - pendingIntent.send(view.context, 0, intent) + val options = ActivityOptions.makeBasic() + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + pendingIntent.send(view.context, 0, intent, null, null, null, options.toBundle()) } catch (e: PendingIntent.CanceledException) { Log.i(TAG, "Unable to send remote input result", e) uiEventLogger.logWithInstanceId( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt index 21d03386b9e2..cac5e3290a26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt @@ -22,6 +22,13 @@ import android.app.PendingIntent import android.app.RemoteInput import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon import android.os.Build import android.os.Bundle import android.os.SystemClock @@ -48,7 +55,13 @@ import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedA import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies +import java.util.concurrent.FutureTask +import java.util.concurrent.SynchronousQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.system.measureTimeMillis + /** Returns whether we should show the smart reply view and its smart suggestions. */ fun shouldShowSmartReplyView( @@ -281,6 +294,51 @@ interface SmartActionInflater { ): Button } +private const val ICON_TASK_TIMEOUT_MS = 500L +private val iconTaskThreadPool = ThreadPoolExecutor(0, 25, 1, TimeUnit.MINUTES, SynchronousQueue()) + +private fun loadIconDrawableWithTimeout( + icon: Icon, + packageContext: Context, + targetSize: Int, +): Drawable? { + if (icon.type != Icon.TYPE_URI && icon.type != Icon.TYPE_URI_ADAPTIVE_BITMAP) { + return icon.loadDrawable(packageContext) + } + val bitmapTask = FutureTask { + val bitmap: Bitmap? + val durationMillis = measureTimeMillis { + val source = ImageDecoder.createSource(packageContext.contentResolver, icon.uri) + bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ -> + decoder.setTargetSize(targetSize, targetSize) + decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT + } + } + if (durationMillis > ICON_TASK_TIMEOUT_MS) { + Log.w(TAG, "Loading $icon took ${durationMillis / 1000f} sec") + } + checkNotNull(bitmap) { "ImageDecoder.decodeBitmap() returned null" } + } + val bitmap = runCatching { + iconTaskThreadPool.execute(bitmapTask) + bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS) + }.getOrElse { ex -> + Log.e(TAG, "Failed to load $icon: $ex") + bitmapTask.cancel(true) + return null + } + // TODO(b/288561520): rewrite Icon so that we don't need to duplicate this logic + val bitmapDrawable = BitmapDrawable(packageContext.resources, bitmap) + val result = if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) + AdaptiveIconDrawable(null, bitmapDrawable) else bitmapDrawable + if (icon.hasTint()) { + result.mutate() + result.setTintList(icon.tintList) + result.setTintBlendMode(icon.tintBlendMode) + } + return result +} + /* internal */ class SmartActionInflaterImpl @Inject constructor( private val constants: SmartReplyConstants, private val activityStarter: ActivityStarter, @@ -304,12 +362,12 @@ interface SmartActionInflater { // We received the Icon from the application - so use the Context of the application to // reference icon resources. - val iconDrawable = action.getIcon().loadDrawable(packageContext) - .apply { - val newIconSize: Int = context.resources.getDimensionPixelSize( - R.dimen.smart_action_button_icon_size) - setBounds(0, 0, newIconSize, newIconSize) - } + val newIconSize = context.resources + .getDimensionPixelSize(R.dimen.smart_action_button_icon_size) + val iconDrawable = + loadIconDrawableWithTimeout(action.getIcon(), packageContext, newIconSize) + ?: GradientDrawable() + iconDrawable.setBounds(0, 0, newIconSize, newIconSize) // Add the action icon to the Smart Action button. setCompoundDrawablesRelative(iconDrawable, null, null, null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index b135d0d8c9dc..1c3a8850df8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -28,6 +28,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings.Global; @@ -122,7 +123,12 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { - updateZenModeConfig(); + try { + Trace.beginSection("updateZenModeConfig"); + updateZenModeConfig(); + } finally { + Trace.endSection(); + } } }; mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt new file mode 100644 index 000000000000..a804923bafdf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt @@ -0,0 +1,39 @@ +/* + * 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.util.wrapper + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.lottie.LottieAnimationView +import com.android.systemui.util.traceSection + +/** LottieAnimationView that traces each call to invalidate. */ +open class LottieViewWrapper : LottieAnimationView { + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) + + override fun invalidate() { + traceSection<Any?>("${this::class} invalidate") { + super.invalidate() + null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 96395b5179f1..ab0d78cdfd6e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -476,7 +476,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index fa32835c2695..677d3ff3df69 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -187,9 +187,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { @Test public void testLockedOut_verifyPasswordAndUnlock_doesNotEnableViewInput() { - mKeyguardAbsKeyInputViewController.handleAttemptLockout( - SystemClock.elapsedRealtime() + 1000); - mKeyguardAbsKeyInputViewController.verifyPasswordAndUnlock(); + mKeyguardAbsKeyInputViewController.handleAttemptLockout(SystemClock.elapsedRealtime()); verify(mAbsKeyInputView).setPasswordEntryInputEnabled(false); verify(mAbsKeyInputView).setPasswordEntryEnabled(false); verify(mAbsKeyInputView, never()).setPasswordEntryInputEnabled(true); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index b21cc6dde815..9e561ed290f7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -408,4 +408,18 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { any(ClockRegistry.ClockChangeListener.class)); verify(mClockEventController, times).registerListeners(mView); } + + @Test + public void testSplitShadeEnabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(true); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void testSplitShadeDisabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(false); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index a2c632936047..512e5dc1a0d6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -155,4 +156,18 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true); verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true); } + + @Test + public void splitShadeEnabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void splitShadeDisabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 1e675f8e4540..3cb4c0c51252 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -2935,6 +2935,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { TelephonyManager.SIM_STATE_UNKNOWN); } + @Test + public void testOnSimStateChanged_HandleSimStateNotReady() { + KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = spy( + KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback); + mKeyguardUpdateMonitor.handleSimStateChange(-1, 0, TelephonyManager.SIM_STATE_NOT_READY); + verify(keyguardUpdateMonitorCallback).onSimStateChanged(-1, 0, + TelephonyManager.SIM_STATE_NOT_READY); + } + private void verifyFingerprintAuthenticateNeverCalled() { verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(), diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index 84e58be2150f..403bd8c0a622 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -48,6 +48,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; @@ -95,6 +96,8 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected @Mock KeyguardTransitionRepository mTransitionRepository; protected @Mock CommandQueue mCommandQueue; protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); + protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; + protected LockIconViewController mUnderTest; @@ -167,7 +170,8 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags, new FakeKeyguardBouncerRepository() ), - mFeatureFlags + mFeatureFlags, + mPrimaryBouncerInteractor ); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index b62875988b2e..ed6a891a6094 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -33,6 +33,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; +import android.view.View; import androidx.test.filters.SmallTest; @@ -267,4 +268,75 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN the lock icon is shown verify(mLockIconView).setContentDescription(LOCKED_LABEL); } + + @Test + public void lockIconAccessibility_notVisibleToUser() { + // GIVEN lock icon controller is initialized and view is attached + init(/* useMigrationFlag= */false); + captureKeyguardStateCallback(); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + // and biometric running state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + reset(mLockIconView); + when(mLockIconView.isVisibleToUser()).thenReturn(false); + + // WHEN the unlocked state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); + mKeyguardStateCallback.onUnlockedChanged(); + + // THEN the lock icon is shown + verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + @Test + public void lockIconAccessibility_bouncerAnimatingAway() { + // GIVEN lock icon controller is initialized and view is attached + init(/* useMigrationFlag= */false); + captureKeyguardStateCallback(); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + // and biometric running state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + reset(mLockIconView); + when(mLockIconView.isVisibleToUser()).thenReturn(true); + when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true); + + // WHEN the unlocked state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); + mKeyguardStateCallback.onUnlockedChanged(); + + // THEN the lock icon is shown + verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + @Test + public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() { + // GIVEN lock icon controller is initialized and view is attached + init(/* useMigrationFlag= */false); + captureKeyguardStateCallback(); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + // and biometric running state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + reset(mLockIconView); + when(mLockIconView.isVisibleToUser()).thenReturn(true); + when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false); + + // WHEN the unlocked state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); + mKeyguardStateCallback.onUnlockedChanged(); + + // THEN the lock icon is shown + verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index b2ccd60216d7..78341915edb7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; +import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -59,6 +60,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.VibrationAttributes; import android.testing.TestableLooper.RunWithLooper; +import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -1184,8 +1186,53 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void fingerDown_falsingManagerInformed() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // THEN falsing manager is informed of the touch + verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); + } + + @Test public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_UP is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.second); + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the new FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + } + + private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent() + throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( @@ -1211,27 +1258,7 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUp); - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the new FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + return new Pair<>(processorResultDown, processorResultUp); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt index 7840525b14aa..021facc51dba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt @@ -146,17 +146,8 @@ class PanelTaskViewControllerTest : SysuiTestCase() { } @Test - fun testTaskViewReleasedOnDismiss() { - underTest.dismiss() - verify(taskView).release() - } - - @Test - fun testTaskViewReleasedOnBackOnRoot() { - underTest.launchTaskView() - verify(taskView).setListener(any(), capture(listenerCaptor)) - - listenerCaptor.value.onBackPressedOnTaskRoot(0) + fun testTaskViewReleasedOnRelease() { + underTest.release() verify(taskView).release() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index c280538f2d30..34ea91b94414 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -31,7 +31,10 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -186,6 +189,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot); when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha()) .thenReturn(mock(Flow.class)); + when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded()) + .thenReturn(mock(Flow.class)); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mViewMediator, mKeyguardBypassController, @@ -238,7 +243,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); - mViewMediator.onWakeAndUnlocking(); + mViewMediator.onWakeAndUnlocking(false); mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false); TestableLooper.get(this).processAllMessages(); @@ -591,8 +596,98 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test public void testWakeAndUnlocking() { - mViewMediator.onWakeAndUnlocking(); + mViewMediator.onWakeAndUnlocking(false); + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + } + + @Test + public void testWakeAndUnlockingOverDream() { + // Send signal to wake + mViewMediator.onWakeAndUnlocking(true); + + // Ensure not woken up yet + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + + // Verify keyguard told of authentication + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(true, + mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); + final ArgumentCaptor<Runnable> animationRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + verify(mStatusBarKeyguardViewManager).startPreHideAnimation( + animationRunnableCaptor.capture()); + + when(mStatusBarStateController.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDozing()).thenReturn(false); + animationRunnableCaptor.getValue().run(); + + when(mKeyguardStateController.isShowing()).thenReturn(false); + mViewMediator.mViewMediatorCallback.keyguardGone(); + + // Verify woken up now. + verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); + } + + @Test + public void testWakeAndUnlockingOverDream_signalAuthenticateIfStillShowing() { + // Send signal to wake + mViewMediator.onWakeAndUnlocking(true); + + // Ensure not woken up yet + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + + // Verify keyguard told of authentication verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + clearInvocations(mStatusBarKeyguardViewManager); + mViewMediator.mViewMediatorCallback.keyguardDonePending(true, + mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); + final ArgumentCaptor<Runnable> animationRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + verify(mStatusBarKeyguardViewManager).startPreHideAnimation( + animationRunnableCaptor.capture()); + + when(mStatusBarStateController.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDozing()).thenReturn(false); + animationRunnableCaptor.getValue().run(); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + + mViewMediator.mViewMediatorCallback.keyguardGone(); + + + // Verify keyguard view controller informed of authentication again + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + } + + @Test + public void testWakeAndUnlockingOverNonInteractiveDream_noWakeByKeyguardViewMediator() { + // Send signal to wake + mViewMediator.onWakeAndUnlocking(false); + + // Ensure not woken up yet + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + + // Verify keyguard told of authentication + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); + mViewMediator.mViewMediatorCallback.keyguardDonePending(true, + mUpdateMonitor.getCurrentUser()); + mViewMediator.mViewMediatorCallback.readyForKeyguardDone(); + final ArgumentCaptor<Runnable> animationRunnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + verify(mStatusBarKeyguardViewManager).startPreHideAnimation( + animationRunnableCaptor.capture()); + + when(mStatusBarStateController.isDreaming()).thenReturn(true); + when(mStatusBarStateController.isDozing()).thenReturn(false); + animationRunnableCaptor.getValue().run(); + + when(mKeyguardStateController.isShowing()).thenReturn(false); + mViewMediator.mViewMediatorCallback.keyguardGone(); + + // Verify not woken up. + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 1090c15d8625..76028e49c757 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -223,6 +223,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { bypassControllerOverride, testScope.backgroundScope, testDispatcher, + testDispatcher, sessionTracker, uiEventLogger, FaceAuthenticationLogger(logcatLogBuffer("DeviceEntryFaceAuthRepositoryLog")), diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index a3413466d62e..ab994b72a45b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -22,8 +22,16 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.mockito.mock import com.google.common.collect.Range @@ -60,7 +68,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -82,7 +90,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this) // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.5f)) @@ -104,7 +112,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.2f)) @@ -126,7 +134,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -138,13 +146,44 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } - private fun step( - value: Float, - state: TransitionState = TransitionState.RUNNING - ): TransitionStep { + @Test + fun transitionEnded() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<TransitionStep>() + + val job = underTest.transitionEnded.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 1.0f, CANCELED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 1.0f, FINISHED)) + + assertThat(values.size).isEqualTo(3) + values.forEach { + assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED) + .isTrue() + } + + job.cancel() + } + + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( - from = KeyguardState.DREAMING, - to = KeyguardState.LOCKSCREEN, + from = DREAMING, + to = LOCKSCREEN, value = value, transitionState = state, ownerName = "DreamingToLockscreenTransitionViewModelTest" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt index 9ab728949e40..530b86eb4978 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt @@ -98,6 +98,8 @@ class MediaResumeListenerTest : SysuiTestCase() { @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback> @Captor lateinit var actionCaptor: ArgumentCaptor<Runnable> @Captor lateinit var componentCaptor: ArgumentCaptor<String> + @Captor lateinit var userIdCaptor: ArgumentCaptor<Int> + @Captor lateinit var userCallbackCaptor: ArgumentCaptor<UserTracker.Callback> private lateinit var executor: FakeExecutor private lateinit var data: MediaData @@ -124,7 +126,7 @@ class MediaResumeListenerTest : SysuiTestCase() { ) Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1) - whenever(resumeBrowserFactory.create(capture(callbackCaptor), any())) + whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), capture(userIdCaptor))) .thenReturn(resumeBrowser) // resume components are stored in sharedpreferences @@ -334,6 +336,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testOnUserUnlock_loadsTracks() { // Set up mock service to successfully find valid media + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) whenever(resumeBrowser.token).thenReturn(token) @@ -417,6 +420,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testLoadComponents_recentlyPlayed_adds() { // Set up browser to return successfully + setUpMbsWithValidResolveInfo() val description = MediaDescription.Builder().setTitle(TITLE).build() val component = ComponentName(PACKAGE_NAME, CLASS_NAME) whenever(resumeBrowser.token).thenReturn(token) @@ -600,7 +604,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // Set up our factory to return a new browser so we can verify we disconnected the old one val newResumeBrowser = mock(ResumeMediaBrowser::class.java) - whenever(resumeBrowserFactory.create(capture(callbackCaptor), any())) + whenever(resumeBrowserFactory.create(capture(callbackCaptor), any(), anyInt())) .thenReturn(newResumeBrowser) // When the resume action is run @@ -610,6 +614,66 @@ class MediaResumeListenerTest : SysuiTestCase() { verify(resumeBrowser).disconnect() } + @Test + fun testUserUnlocked_userChangeWhileQuerying() { + val firstUserId = 1 + val secondUserId = 2 + val description = MediaDescription.Builder().setTitle(TITLE).build() + val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + + setUpMbsWithValidResolveInfo() + whenever(resumeBrowser.token).thenReturn(token) + whenever(resumeBrowser.appIntent).thenReturn(pendingIntent) + + val unlockIntent = + Intent(Intent.ACTION_USER_UNLOCKED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, firstUserId) + } + verify(userTracker).addCallback(capture(userCallbackCaptor), any()) + + // When the first user unlocks and we query their recent media + userCallbackCaptor.value.onUserChanged(firstUserId, context) + resumeListener.userUnlockReceiver.onReceive(context, unlockIntent) + whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value) + verify(resumeBrowser, times(3)).findRecentMedia() + + // And the user changes before the MBS response is received + userCallbackCaptor.value.onUserChanged(secondUserId, context) + callbackCaptor.value.addTrack(description, component, resumeBrowser) + + // Then the loaded media is correctly associated with the first user + verify(mediaDataManager) + .addResumptionControls( + eq(firstUserId), + eq(description), + any(), + eq(token), + eq(PACKAGE_NAME), + eq(pendingIntent), + eq(PACKAGE_NAME) + ) + } + + @Test + fun testUserUnlocked_noComponent_doesNotQuery() { + // Set up a valid MBS, but user does not have the service available + setUpMbsWithValidResolveInfo() + val pm = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(pm) + whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(null) + + val unlockIntent = + Intent(Intent.ACTION_USER_UNLOCKED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + } + + // When the user is unlocked, but does not have the component installed + resumeListener.userUnlockReceiver.onReceive(context, unlockIntent) + + // Then we never attempt to connect to it + verify(resumeBrowser, never()).findRecentMedia() + } + /** Sets up mocks to successfully find a MBS that returns valid media. */ private fun setUpMbsWithValidResolveInfo() { val pm = mock(PackageManager::class.java) @@ -620,6 +684,8 @@ class MediaResumeListenerTest : SysuiTestCase() { resolveInfo.serviceInfo = serviceInfo resolveInfo.serviceInfo.name = CLASS_NAME val resumeInfo = listOf(resolveInfo) - whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + whenever(pm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(resumeInfo) + whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo) + whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt index a04cfd46588b..b45e66bfc31b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt @@ -93,7 +93,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { component, browserFactory, logger, - mediaController + mediaController, + context.userId, ) } @@ -381,8 +382,9 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { componentName: ComponentName, browserFactory: MediaBrowserFactory, logger: ResumeMediaBrowserLogger, - private val fakeController: MediaController - ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger) { + private val fakeController: MediaController, + userId: Int, + ) : ResumeMediaBrowser(context, callback, componentName, browserFactory, logger, userId) { override fun createMediaController(token: MediaSession.Token): MediaController { return fakeController diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index b40ebc9bb156..91b0245be8d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -193,6 +193,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test + fun dozeWakeUpAnimationWaiting_inSplitShade_mediaIsHidden() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = true + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(splitShadeContainer.visibility).isEqualTo(GONE) + } + + @Test fun dozing_inSingleShade_mediaIsVisible() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) @@ -203,6 +214,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } + @Test + fun dozeWakeUpAnimationWaiting_inSingleShade_mediaIsVisible() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = false + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) + } + private fun setDozing() { whenever(statusBarStateController.isDozing).thenReturn(true) statusBarStateListener.onDozingChanged(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 7df54d44e69e..e4f89a226a34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -291,13 +291,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -525,16 +525,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT); assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo( TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue(); + assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse(); } @Test 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 8f2ee91d6a6a..987e09c8776c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -510,6 +510,17 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() { + when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); + mStatusBarStateController.setState(KEYGUARD); + enableSplitShade(/* enabled= */ true); + + mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true); + + verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true); + } + + @Test public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); mStatusBarStateController.setState(KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 540bda6ea9dc..9037df821ca8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -1675,11 +1675,21 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testCanDismissOtherNotificationChildren() { + // GIVEN an ongoing notification + final NotificationEntry container = new NotificationEntryBuilder() + .setGroup(mContext, "group") + .build(); + + // THEN its children are dismissible + assertTrue(mCollection.shouldAutoDismissChildren( + container, container.getSbn().getGroupKey())); + } + + @Test public void testCannotDismissOngoingNotificationChildren() { // GIVEN an ongoing notification final NotificationEntry container = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE) - .setId(47) .setGroup(mContext, "group") .setFlag(mContext, FLAG_ONGOING_EVENT, true) .build(); @@ -1693,6 +1703,7 @@ public class NotifCollectionTest extends SysuiTestCase { public void testCannotDismissNoClearNotifications() { // GIVEN an no-clear notification final NotificationEntry container = new NotificationEntryBuilder() + .setGroup(mContext, "group") .setFlag(mContext, FLAG_NO_CLEAR, true) .build(); @@ -1702,11 +1713,25 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testCannotDismissPriorityConversations() { + // GIVEN an no-clear notification + NotificationChannel channel = + new NotificationChannel("foo", "Foo", NotificationManager.IMPORTANCE_HIGH); + channel.setImportantConversation(true); + final NotificationEntry container = new NotificationEntryBuilder() + .setGroup(mContext, "group") + .setChannel(channel) + .build(); + + // THEN its children are not dismissible + assertFalse(mCollection.shouldAutoDismissChildren( + container, container.getSbn().getGroupKey())); + } + + @Test public void testCanDismissFgsNotificationChildren() { // GIVEN an FGS but not ongoing notification final NotificationEntry container = new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE) - .setId(47) .setGroup(mContext, "group") .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true) .build(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 02666e40b98d..cd0550e13540 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -74,6 +74,7 @@ import com.android.systemui.statusbar.notification.collection.render.SectionHead import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; +import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; @@ -339,6 +340,36 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + public void callSwipeCallbacksDuringClearAll() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(true); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test + public void callSwipeCallbacksDuringClearNotification() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(false); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test public void testOnMenuClickedLogging() { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 89f8bdbfe05b..7acf398955fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -186,7 +186,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT, BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */); - verify(mKeyguardViewMediator).onWakeAndUnlocking(); + verify(mKeyguardViewMediator).onWakeAndUnlocking(false); assertThat(mBiometricUnlockController.getMode()) .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING); } @@ -204,7 +204,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT, BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */); - verify(mKeyguardViewMediator).onWakeAndUnlocking(); + verify(mKeyguardViewMediator).onWakeAndUnlocking(false); assertThat(mBiometricUnlockController.getMode()) .isEqualTo(MODE_WAKE_AND_UNLOCK); } @@ -542,4 +542,21 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); when(mKeyguardStateController.isShowing()).thenReturn(true); } + + private void givenDreamingLocked() { + when(mUpdateMonitor.isDreaming()).thenReturn(true); + when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + } + @Test + public void onSideFingerprintSuccess_dreaming_unlockNoWake() { + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + givenDreamingLocked(); + when(mPowerManager.isInteractive()).thenReturn(true); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); + verify(mKeyguardViewMediator).onWakeAndUnlocking(true); + // Ensure that the power hasn't been told to wake up yet. + verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt index c0243dc537b5..b2dc0dc984c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt @@ -105,6 +105,30 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS) } + /** Regression test for b/287508741 */ + @Test + fun getLetterboxAppearance_withOverlap_doesNotMutateOriginalBounds() { + val statusBarStartSideBounds = Rect(left = 0, top = 0, right = 100, bottom = 100) + val statusBarEndSideBounds = Rect(left = 200, top = 0, right = 300, bottom = 100) + val letterBoxInnerBounds = Rect(left = 150, top = 50, right = 250, bottom = 150) + val statusBarStartSideBoundsCopy = Rect(statusBarStartSideBounds) + val statusBarEndSideBoundsCopy = Rect(statusBarEndSideBounds) + val letterBoxInnerBoundsCopy = Rect(letterBoxInnerBounds) + whenever(statusBarBoundsProvider.visibleStartSideBounds) + .thenReturn(statusBarStartSideBounds) + whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(statusBarEndSideBounds) + + calculator.getLetterboxAppearance( + TEST_APPEARANCE, + TEST_APPEARANCE_REGIONS, + arrayOf(letterboxWithInnerBounds(letterBoxInnerBounds)) + ) + + expect.that(statusBarStartSideBounds).isEqualTo(statusBarStartSideBoundsCopy) + expect.that(statusBarEndSideBounds).isEqualTo(statusBarEndSideBoundsCopy) + expect.that(letterBoxInnerBounds).isEqualTo(letterBoxInnerBoundsCopy) + } + @Test fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() { whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 3eea93c6ec2c..131cc07178f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.service.trust.TrustAgentService; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -57,6 +58,8 @@ import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; @@ -84,7 +87,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.google.common.truth.Truth; @@ -153,7 +155,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Captor private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor; @Captor - private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; + private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback; @Before @@ -925,18 +927,24 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void onDeviceUnlocked_hideAlternateBouncerAndClearMessageArea() { + public void onTrustChanged_hideAlternateBouncerAndClearMessageArea() { + // GIVEN keyguard update monitor callback is registered + verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture()); + reset(mKeyguardUpdateMonitor); reset(mKeyguardMessageAreaController); - // GIVEN keyguard state controller callback is registered - verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture()); - // GIVEN alternate bouncer state = not visible when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - // WHEN the device is unlocked - mKeyguardStateControllerCallback.getValue().onUnlockedChanged(); + // WHEN the device is trusted by active unlock + mKeyguardUpdateMonitorCallback.getValue().onTrustGrantedForCurrentUser( + true, + true, + new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD + | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE), + null + ); // THEN the false visibility state is propagated to the keyguardUpdateMonitor verify(mKeyguardUpdateMonitor).setAlternateBouncerShowing(eq(false)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index fd156d86c19e..9aea70f9cdb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -1074,13 +1074,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(configFromContext.showAtLeast3G).isTrue() // WHEN the change event is fired - fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> - receiver.onReceive( - context, - Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) - .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID) - ) - } + val intent = + Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent) // THEN the config is updated assertTrue(latest!!.areEqual(configFromContext)) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index af940e4fa687..f19e19113b30 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -18,6 +18,7 @@ package com.android.systemui.broadcast import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent import android.content.IntentFilter import android.os.Handler import android.os.Looper @@ -31,6 +32,14 @@ import java.lang.IllegalStateException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor +/** + * A fake instance of [BroadcastDispatcher] for tests. + * + * Important: The *real* broadcast dispatcher will only send intents to receivers if the intent + * matches the [IntentFilter] that the [BroadcastReceiver] was registered with. This fake class does + * *not* do that matching by default. Use [sendIntentToMatchingReceiversOnly] to get the same + * matching behavior as the real broadcast dispatcher. + */ class FakeBroadcastDispatcher( context: SysuiTestableContext, mainExecutor: Executor, @@ -52,7 +61,10 @@ class FakeBroadcastDispatcher( PendingRemovalStore(logger) ) { - val registeredReceivers: MutableSet<BroadcastReceiver> = ConcurrentHashMap.newKeySet() + private val receivers: MutableSet<InternalReceiver> = ConcurrentHashMap.newKeySet() + + val registeredReceivers: Set<BroadcastReceiver> + get() = receivers.map { it.receiver }.toSet() override fun registerReceiverWithHandler( receiver: BroadcastReceiver, @@ -62,7 +74,7 @@ class FakeBroadcastDispatcher( @Context.RegisterReceiverFlags flags: Int, permission: String? ) { - registeredReceivers.add(receiver) + receivers.add(InternalReceiver(receiver, filter)) } override fun registerReceiver( @@ -73,15 +85,34 @@ class FakeBroadcastDispatcher( @Context.RegisterReceiverFlags flags: Int, permission: String? ) { - registeredReceivers.add(receiver) + receivers.add(InternalReceiver(receiver, filter)) } override fun unregisterReceiver(receiver: BroadcastReceiver) { - registeredReceivers.remove(receiver) + receivers.removeIf { it.receiver == receiver } } override fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) { - registeredReceivers.remove(receiver) + receivers.removeIf { it.receiver == receiver } + } + + /** + * Sends the given [intent] to *only* the receivers that were registered with an [IntentFilter] + * that matches the intent. + */ + fun sendIntentToMatchingReceiversOnly(context: Context, intent: Intent) { + receivers.forEach { + if ( + it.filter.match( + context.contentResolver, + intent, + /* resolve= */ false, + /* logTag= */ "FakeBroadcastDispatcher", + ) > 0 + ) { + it.receiver.onReceive(context, intent) + } + } } fun cleanUpReceivers(testName: String) { @@ -91,6 +122,11 @@ class FakeBroadcastDispatcher( throw IllegalStateException("Receiver not unregistered from dispatcher: $it") } } - registeredReceivers.clear() + receivers.clear() } + + private data class InternalReceiver( + val receiver: BroadcastReceiver, + val filter: IntentFilter, + ) } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 5da0575200f6..5fec8494fb92 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4996,7 +4996,10 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle(); - Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class); + Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); + if (intent != null && intent.getClass() != Intent.class) { + return false; + } Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT, Intent.class); if (intent == null) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index c1239d53058c..d5f41a109042 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3701,9 +3701,7 @@ public final class ActiveServices { } clientPsr.addConnection(c); c.startAssociationIfNeeded(); - // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from - // dropping the process' adjustment level. - if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) { + if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) { clientPsr.setHasAboveClient(true); } if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) { diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 7ff6d116baaf..81d0b6ac700b 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -341,8 +341,7 @@ final class ProcessServiceRecord { mHasAboveClient = false; for (int i = mConnections.size() - 1; i >= 0; i--) { ConnectionRecord cr = mConnections.valueAt(i); - if (cr.binding.service.app.mServices != this - && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { + if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) { mHasAboveClient = true; break; } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index eb7fa1069b7b..add94b1bf937 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -172,6 +172,7 @@ public final class DeviceStateManagerService extends SystemService { private DeviceState mRearDisplayState; // TODO(259328837) Generalize for all pending feature requests in the future + @GuardedBy("mLock") @Nullable private OverrideRequest mRearDisplayPendingOverrideRequest; @@ -779,7 +780,7 @@ public final class DeviceStateManagerService extends SystemService { * {@link StatusBarManagerInternal} to notify SystemUI to display the educational dialog. */ @GuardedBy("mLock") - private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) { + private void showRearDisplayEducationalOverlayLocked(@NonNull OverrideRequest request) { mRearDisplayPendingOverrideRequest = request; StatusBarManagerInternal statusBar = @@ -844,8 +845,8 @@ public final class DeviceStateManagerService extends SystemService { * request if it was dismissed in a way that should cancel the feature. */ private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) { - if (mRearDisplayPendingOverrideRequest != null) { - synchronized (mLock) { + synchronized (mLock) { + if (mRearDisplayPendingOverrideRequest != null) { if (shouldCancelRequest) { ProcessRecord processRecord = mProcessRecords.get( mRearDisplayPendingOverrideRequest.getPid()); diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java new file mode 100644 index 000000000000..03103834f325 --- /dev/null +++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java @@ -0,0 +1,50 @@ +/* + * 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.feature; + +import android.hardware.display.DisplayManager; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; + +import java.util.concurrent.Executor; + +/** + * Helper class to access all DeviceConfig features for display_manager namespace + * + **/ +public class DeviceConfigParameterProvider { + + private static final String TAG = "DisplayFeatureProvider"; + + private final DeviceConfigInterface mDeviceConfig; + + public DeviceConfigParameterProvider(DeviceConfigInterface deviceConfig) { + mDeviceConfig = deviceConfig; + } + + public boolean isDisableScreenWakeLocksWhileCachedFeatureEnabled() { + return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_DISABLE_SCREEN_WAKE_LOCKS_WHILE_CACHED, true); + } + + /** add property change listener to DeviceConfig */ + public void addOnPropertiesChangedListener(Executor executor, + DeviceConfig.OnPropertiesChangedListener listener) { + mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + executor, listener); + } +} diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 6d70d21e3b84..da93d0b0fc35 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -18,6 +18,8 @@ package com.android.server.dreams; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; +import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; +import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; @@ -72,6 +74,7 @@ final class DreamController { private final Handler mHandler; private final Listener mListener; private final ActivityTaskManager mActivityTaskManager; + private final PowerManager mPowerManager; private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND); @@ -84,6 +87,15 @@ final class DreamController { private final Intent mCloseNotificationShadeIntent; private final Bundle mCloseNotificationShadeOptions; + /** + * If this flag is on, we report user activity to {@link PowerManager} so that the screen + * doesn't shut off immediately when a dream quits unexpectedly. The device will instead go to + * keyguard and time out back to dreaming shortly. + * + * This allows the dream a second chance to relaunch in case of an app update or other crash. + */ + private final boolean mResetScreenTimeoutOnUnexpectedDreamExit; + private DreamRecord mCurrentDream; // Whether a dreaming started intent has been broadcast. @@ -101,6 +113,7 @@ final class DreamController { mHandler = handler; mListener = listener; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); + mPowerManager = mContext.getSystemService(PowerManager.class); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent.putExtra(EXTRA_REASON_KEY, EXTRA_REASON_VALUE); mCloseNotificationShadeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -110,6 +123,8 @@ final class DreamController { EXTRA_REASON_VALUE) .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); + mResetScreenTimeoutOnUnexpectedDreamExit = context.getResources().getBoolean( + com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit); } /** @@ -214,6 +229,17 @@ final class DreamController { } /** + * Sends a user activity signal to PowerManager to stop the screen from turning off immediately + * if there hasn't been any user interaction in a while. + */ + private void resetScreenTimeout() { + Slog.i(TAG, "Resetting screen timeout"); + long time = SystemClock.uptimeMillis(); + mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER, + USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS); + } + + /** * Stops dreaming. * * The current dream, if any, and any unstopped previous dreams are stopped. The device stops @@ -420,6 +446,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { + if (mResetScreenTimeoutOnUnexpectedDreamExit) { + resetScreenTimeout(); + } stopDream(true /*immediate*/, "binder died"); } }); @@ -445,6 +474,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { + if (mResetScreenTimeoutOnUnexpectedDreamExit) { + resetScreenTimeout(); + } stopDream(true /*immediate*/, "service disconnected"); } }); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index e9f4bd058297..0a02c49192b9 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -873,6 +873,10 @@ public class LockSettingsService extends ILockSettings.Stub { getAuthSecretHal(); mDeviceProvisionedObserver.onSystemReady(); + // Work around an issue in PropertyInvalidatedCache where the cache doesn't work until the + // first invalidation. This can be removed if PropertyInvalidatedCache is fixed. + LockPatternUtils.invalidateCredentialTypeCache(); + // TODO: maybe skip this for split system user mode. mStorage.prefetchUser(UserHandle.USER_SYSTEM); mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(), diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 488745c6ea97..7369e5ed5932 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5594,6 +5594,11 @@ public class NotificationManagerService extends SystemService { boolean granted, boolean userSet) { Objects.requireNonNull(listener); checkNotificationListenerAccess(); + if (granted && listener.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { + throw new IllegalArgumentException( + "Component name too long: " + listener.flattenToString()); + } if (!userSet && isNotificationListenerAccessUserSet(listener)) { // Don't override user's choice return; @@ -7000,7 +7005,7 @@ public class NotificationManagerService extends SystemService { */ private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) { return notification.isMediaNotification() || isEnterpriseExempted(ai) - || isCallNotification(ai.packageName, ai.uid, notification) + || notification.isStyle(Notification.CallStyle.class) || isDefaultSearchSelectorPackage(ai.packageName); } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index 0cc4fc4e0516..f26d56ec28a0 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -30,6 +30,7 @@ import android.app.Person; import android.os.Bundle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; +import android.util.Log; import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEvent; @@ -45,6 +46,8 @@ import java.util.Objects; */ interface NotificationRecordLogger { + static final String TAG = "NotificationRecordLogger"; + // The high-level interface used by clients. /** @@ -225,51 +228,40 @@ interface NotificationRecordLogger { @NotificationStats.DismissalSurface int surface) { // Shouldn't be possible to get a non-dismissed notification here. if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) { - if (NotificationManagerService.DBG) { - throw new IllegalArgumentException("Unexpected surface " + surface); - } + Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason); return INVALID; } - // Most cancel reasons do not have a meaningful surface. Reason codes map directly - // to NotificationCancelledEvent codes. - if (surface == NotificationStats.DISMISSAL_OTHER) { + + // User cancels have a meaningful surface, which we differentiate by. See b/149038335 + // for caveats. + if (reason == REASON_CANCEL) { + switch (surface) { + case NotificationStats.DISMISSAL_PEEK: + return NOTIFICATION_CANCEL_USER_PEEK; + case NotificationStats.DISMISSAL_AOD: + return NOTIFICATION_CANCEL_USER_AOD; + case NotificationStats.DISMISSAL_SHADE: + return NOTIFICATION_CANCEL_USER_SHADE; + case NotificationStats.DISMISSAL_BUBBLE: + return NOTIFICATION_CANCEL_USER_BUBBLE; + case NotificationStats.DISMISSAL_LOCKSCREEN: + return NOTIFICATION_CANCEL_USER_LOCKSCREEN; + case NotificationStats.DISMISSAL_OTHER: + return NOTIFICATION_CANCEL_USER_OTHER; + default: + Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason); + return INVALID; + } + } else { if ((REASON_CLICK <= reason) && (reason <= REASON_CLEAR_DATA)) { return NotificationCancelledEvent.values()[reason]; } if (reason == REASON_ASSISTANT_CANCEL) { return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT; } - if (NotificationManagerService.DBG) { - throw new IllegalArgumentException("Unexpected cancel reason " + reason); - } + Log.wtf(TAG, "Unexpected reason: " + reason + " with surface " + surface); return INVALID; } - // User cancels have a meaningful surface, which we differentiate by. See b/149038335 - // for caveats. - if (reason != REASON_CANCEL) { - if (NotificationManagerService.DBG) { - throw new IllegalArgumentException("Unexpected cancel with surface " + reason); - } - return INVALID; - } - switch (surface) { - case NotificationStats.DISMISSAL_PEEK: - return NOTIFICATION_CANCEL_USER_PEEK; - case NotificationStats.DISMISSAL_AOD: - return NOTIFICATION_CANCEL_USER_AOD; - case NotificationStats.DISMISSAL_SHADE: - return NOTIFICATION_CANCEL_USER_SHADE; - case NotificationStats.DISMISSAL_BUBBLE: - return NOTIFICATION_CANCEL_USER_BUBBLE; - case NotificationStats.DISMISSAL_LOCKSCREEN: - return NOTIFICATION_CANCEL_USER_LOCKSCREEN; - default: - if (NotificationManagerService.DBG) { - throw new IllegalArgumentException("Unexpected surface for user-dismiss " - + reason); - } - return INVALID; - } } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 50f1673cae44..622cb6609630 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -110,6 +110,7 @@ import android.app.backup.IBackupManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.DataLoaderType; @@ -119,7 +120,6 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; -import android.content.pm.ResolveInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; @@ -166,6 +166,7 @@ import com.android.internal.util.CollectionUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; +import com.android.server.SystemConfig; import com.android.server.art.model.DexoptParams; import com.android.server.art.model.DexoptResult; import com.android.server.pm.Installer.LegacyDexoptDisabledException; @@ -184,7 +185,9 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; @@ -207,6 +210,7 @@ import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -227,6 +231,7 @@ final class InstallPackageHelper { private final ViewCompiler mViewCompiler; private final SharedLibrariesImpl mSharedLibraries; private final PackageManagerServiceInjector mInjector; + private final UpdateOwnershipHelper mUpdateOwnershipHelper; // TODO(b/198166813): remove PMS dependency InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { @@ -244,6 +249,7 @@ final class InstallPackageHelper { mPackageAbiHelper = pm.mInjector.getAbiHelper(); mViewCompiler = pm.mInjector.getViewCompiler(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); + mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); } InstallPackageHelper(PackageManagerService pm) { @@ -329,6 +335,8 @@ final class InstallPackageHelper { final String updateOwnerFromSysconfig = isApex || !pkgSetting.isSystem() ? null : mPm.mInjector.getSystemConfig().getSystemAppUpdateOwnerPackageName( parsedPackage.getPackageName()); + final boolean isUpdateOwnershipDenylisted = + mUpdateOwnershipHelper.isUpdateOwnershipDenylisted(parsedPackage.getPackageName()); final boolean isUpdateOwnershipEnabled = oldUpdateOwner != null; // For standard install (install via session), the installSource isn't null. @@ -364,6 +372,9 @@ final class InstallPackageHelper { & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0; final boolean isSameUpdateOwner = TextUtils.equals(oldUpdateOwner, installSource.mInstallerPackageName); + final boolean isInstallerUpdateOwnerDenylistProvider = + mUpdateOwnershipHelper.isUpdateOwnershipDenyListProvider( + installSource.mUpdateOwnerPackageName); // Here we handle the update owner for the package, and the rules are: // -. Only enabling update ownership enforcement on initial installation if the @@ -371,13 +382,16 @@ final class InstallPackageHelper { // -. Once the installer changes and users agree to proceed, clear the update // owner (package state in other users are taken into account as well). if (!isUpdate) { - if (!isRequestUpdateOwnership) { + if (!isRequestUpdateOwnership + || isUpdateOwnershipDenylisted + || isInstallerUpdateOwnerDenylistProvider) { installSource = installSource.setUpdateOwnerPackageName(null); } else if ((!isUpdateOwnershipEnabled && pkgAlreadyExists) || (isUpdateOwnershipEnabled && !isSameUpdateOwner)) { installSource = installSource.setUpdateOwnerPackageName(null); } - } else if (!isSameUpdateOwner || !isUpdateOwnershipEnabled) { + } else if (!isSameUpdateOwner + || !isUpdateOwnershipEnabled) { installSource = installSource.setUpdateOwnerPackageName(null); } } @@ -470,6 +484,19 @@ final class InstallPackageHelper { pkgSetting.setLoadingProgress(1f); } + ArraySet<String> listItems = mUpdateOwnershipHelper.readUpdateOwnerDenyList(pkgSetting); + if (listItems != null && !listItems.isEmpty()) { + mUpdateOwnershipHelper.addToUpdateOwnerDenyList(pkgSetting.getPackageName(), listItems); + for (String unownedPackage : listItems) { + PackageSetting unownedSetting = mPm.mSettings.getPackageLPr(unownedPackage); + SystemConfig config = SystemConfig.getInstance(); + if (unownedSetting != null + && config.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) { + unownedSetting.setUpdateOwnerPackage(null); + } + } + } + return pkg; } @@ -3925,23 +3952,6 @@ final class InstallPackageHelper { } } - // If this is a system app we hadn't seen before, and this is a first boot or OTA, - // we need to unstop it if it doesn't have a launcher entry. - if (mPm.mShouldStopSystemPackagesByDefault && scanResult.mRequest.mPkgSetting == null - && ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) - && ((scanFlags & SCAN_AS_SYSTEM) != 0)) { - final Intent launcherIntent = new Intent(Intent.ACTION_MAIN); - launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); - launcherIntent.setPackage(parsedPackage.getPackageName()); - final List<ResolveInfo> launcherActivities = - mPm.snapshotComputer().queryIntentActivitiesInternal(launcherIntent, null, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0); - if (launcherActivities.isEmpty()) { - scanResult.mPkgSetting.setStopped(false, 0); - } - } - if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) { if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) { // Continue monitoring loading progress of active incremental packages @@ -4314,6 +4324,8 @@ final class InstallPackageHelper { // - It's an APEX or overlay package since stopped state does not affect them. // - It is enumerated with a <initial-package-state> tag having the stopped attribute // set to false + // - It doesn't have an enabled and exported launcher activity, which means the user + // wouldn't have a way to un-stop it final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0; if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition @@ -4322,8 +4334,9 @@ final class InstallPackageHelper { && !parsedPackage.isOverlayIsStatic() ) { String packageName = parsedPackage.getPackageName(); - if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName) - && !"android".contentEquals(packageName)) { + if (!"android".contentEquals(packageName) + && !mPm.mInitialNonStoppedSystemPackages.contains(packageName) + && hasLauncherEntry(parsedPackage)) { scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP; } } @@ -4333,6 +4346,26 @@ final class InstallPackageHelper { return new Pair<>(scanResult, shouldHideSystemApp); } + private static boolean hasLauncherEntry(ParsedPackage parsedPackage) { + final HashSet<String> categories = new HashSet<>(); + categories.add(Intent.CATEGORY_LAUNCHER); + final List<ParsedActivity> activities = parsedPackage.getActivities(); + for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) { + final ParsedActivity activity = activities.get(indexActivity); + if (!activity.isEnabled() || !activity.isExported()) { + continue; + } + final List<ParsedIntentInfo> intents = activity.getIntents(); + for (int indexIntent = 0; indexIntent < intents.size(); indexIntent++) { + final IntentFilter intentFilter = intents.get(indexIntent).getIntentFilter(); + if (intentFilter != null && intentFilter.matchCategories(categories) == null) { + return true; + } + } + } + return false; + } + /** * Returns if forced apk verification can be skipped for the whole package, including splits. */ diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index db47306ad58e..f2b62eaf25f8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1607,7 +1607,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService (i, pm) -> new SharedLibrariesImpl(pm, i), (i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(), i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(), - context)); + context), + (i, pm) -> new UpdateOwnershipHelper()); if (Build.VERSION.SDK_INT <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index 13549f536c9f..51840e70ed26 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -144,6 +144,7 @@ public class PackageManagerServiceInjector { private final Singleton<IBackupManager> mIBackupManager; private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer; private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer; + private final Singleton<UpdateOwnershipHelper> mUpdateOwnershipHelperProducer; PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock, Installer installer, Object installLock, PackageAbiHelper abiHelper, @@ -183,7 +184,8 @@ public class PackageManagerServiceInjector { Producer<BackgroundDexOptService> backgroundDexOptService, Producer<IBackupManager> iBackupManager, Producer<SharedLibrariesImpl> sharedLibrariesProducer, - Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer) { + Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer, + Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) { mContext = context; mLock = lock; mInstaller = installer; @@ -238,6 +240,7 @@ public class PackageManagerServiceInjector { mSharedLibrariesProducer = new Singleton<>(sharedLibrariesProducer); mCrossProfileIntentFilterHelperProducer = new Singleton<>( crossProfileIntentFilterHelperProducer); + mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer); } /** @@ -423,6 +426,11 @@ public class PackageManagerServiceInjector { return mSharedLibrariesProducer.get(this, mPackageManager); } + public UpdateOwnershipHelper getUpdateOwnershipHelper() { + return mUpdateOwnershipHelperProducer.get(this, mPackageManager); + } + + /** Provides an abstraction to static access to system state. */ public interface SystemWrapper { void disablePackageCaches(); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 10673c6f2dd2..59314a26ab97 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -312,6 +312,7 @@ final class RemovePackageHelper { synchronized (mPm.mLock) { mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName()); mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName); + mPm.mInjector.getUpdateOwnershipHelper().removeUpdateOwnerDenyList(packageName); final Computer snapshot = mPm.snapshotComputer(); mPm.mAppsFilter.removePackage(snapshot, snapshot.getPackageStateInternal(packageName)); diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java new file mode 100644 index 000000000000..43752f31a1a2 --- /dev/null +++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java @@ -0,0 +1,185 @@ +/* + * 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.pm; + +import static android.content.pm.PackageManager.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST; + +import static com.android.server.pm.PackageManagerService.TAG; + +import android.Manifest; +import android.app.ResourcesManager; +import android.content.pm.ApplicationInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.component.ParsedUsesPermission; + +import org.xmlpull.v1.XmlPullParser; + +import java.util.List; + +/** Helper class for managing update ownership and optouts for the feature. */ +public class UpdateOwnershipHelper { + + // Called out in PackageManager.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST docs + private static final int MAX_DENYLIST_SIZE = 500; + private static final String TAG_OWNERSHIP_OPT_OUT = "deny-ownership"; + private final ArrayMap<String, ArraySet<String>> mUpdateOwnerOptOutsToOwners = + new ArrayMap<>(200); + + private final Object mLock = new Object(); + + private static boolean hasValidOwnershipDenyList(PackageSetting pkgSetting) { + AndroidPackage pkg = pkgSetting.getPkg(); + // we're checking for uses-permission for these priv permissions instead of grant as we're + // only considering system apps to begin with, so presumed to be granted. + return pkg != null + && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp()) + && pkg.getProperties().containsKey(PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST) + && usesAnyPermission(pkg, + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.INSTALL_PACKAGE_UPDATES); + } + + + /** Returns true if a package setting declares that it uses a permission */ + private static boolean usesAnyPermission(AndroidPackage pkgSetting, String... permissions) { + List<ParsedUsesPermission> usesPermissions = pkgSetting.getUsesPermissions(); + for (int i = 0; i < usesPermissions.size(); i++) { + for (int j = 0; j < permissions.length; j++) { + if (permissions[j].equals(usesPermissions.get(i).getName())) { + return true; + } + } + } + return false; + } + + /** + * Reads the update owner deny list from a {@link PackageSetting} and returns the set of + * packages it contains or {@code null} if it cannot be read. + */ + public ArraySet<String> readUpdateOwnerDenyList(PackageSetting pkgSetting) { + if (!hasValidOwnershipDenyList(pkgSetting)) { + return null; + } + AndroidPackage pkg = pkgSetting.getPkg(); + if (pkg == null) { + return null; + } + ArraySet<String> ownershipDenyList = new ArraySet<>(MAX_DENYLIST_SIZE); + try { + int resId = pkg.getProperties().get(PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST) + .getResourceId(); + ApplicationInfo appInfo = AndroidPackageUtils.generateAppInfoWithoutState(pkg); + Resources resources = ResourcesManager.getInstance().getResources( + null, appInfo.sourceDir, appInfo.splitSourceDirs, appInfo.resourceDirs, + appInfo.overlayPaths, appInfo.sharedLibraryFiles, null, Configuration.EMPTY, + null, null, null); + try (XmlResourceParser parser = resources.getXml(resId)) { + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if (parser.next() == XmlResourceParser.START_TAG) { + if (TAG_OWNERSHIP_OPT_OUT.equals(parser.getName())) { + parser.next(); + String packageName = parser.getText(); + if (packageName != null && !packageName.isBlank()) { + ownershipDenyList.add(packageName); + if (ownershipDenyList.size() > MAX_DENYLIST_SIZE) { + Slog.w(TAG, "Deny list defined by " + pkg.getPackageName() + + " was trucated to maximum size of " + + MAX_DENYLIST_SIZE); + break; + } + } + } + } + } + } + } catch (Exception e) { + Slog.e(TAG, "Failed to parse update owner list for " + pkgSetting.getPackageName(), e); + return null; + } + return ownershipDenyList; + } + + /** + * Begins tracking the contents of a deny list and the owner of that deny list for use in calls + * to {@link #isUpdateOwnershipDenylisted(String)} and + * {@link #isUpdateOwnershipDenyListProvider(String)}. + * + * @param listOwner the packageName of the package that owns the deny list. + * @param listContents the list of packageNames that are on the deny list. + */ + public void addToUpdateOwnerDenyList(String listOwner, ArraySet<String> listContents) { + synchronized (mLock) { + for (int i = 0; i < listContents.size(); i++) { + String packageName = listContents.valueAt(i); + ArraySet<String> priorDenyListOwners = mUpdateOwnerOptOutsToOwners.putIfAbsent( + packageName, new ArraySet<>(new String[]{listOwner})); + if (priorDenyListOwners != null) { + priorDenyListOwners.add(listOwner); + } + } + } + } + + /** + * Stop tracking the contents of a deny list owned by the provided owner of the deny list. + * @param listOwner the packageName of the package that owns the deny list. + */ + public void removeUpdateOwnerDenyList(String listOwner) { + synchronized (mLock) { + for (int i = mUpdateOwnerOptOutsToOwners.size() - 1; i >= 0; i--) { + ArraySet<String> packageDenyListContributors = + mUpdateOwnerOptOutsToOwners.get(mUpdateOwnerOptOutsToOwners.keyAt(i)); + if (packageDenyListContributors.remove(listOwner) + && packageDenyListContributors.isEmpty()) { + mUpdateOwnerOptOutsToOwners.removeAt(i); + } + } + } + } + + /** + * Returns {@code true} if the provided package name is on a valid update ownership deny list. + */ + public boolean isUpdateOwnershipDenylisted(String packageName) { + return mUpdateOwnerOptOutsToOwners.containsKey(packageName); + } + + /** + * Returns {@code true} if the provided package name defines a valid update ownership deny list. + */ + public boolean isUpdateOwnershipDenyListProvider(String packageName) { + if (packageName == null) { + return false; + } + synchronized (mLock) { + for (int i = mUpdateOwnerOptOutsToOwners.size() - 1; i >= 0; i--) { + if (mUpdateOwnerOptOutsToOwners.valueAt(i).contains(packageName)) { + return true; + } + } + return false; + } + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5fc557a06df9..473573afd2c5 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -702,8 +702,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { finishKeyguardDrawn(); break; case MSG_WINDOW_MANAGER_DRAWN_COMPLETE: - if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete"); - finishWindowsDrawn(msg.arg1); + final int displayId = msg.arg1; + if (DEBUG_WAKEUP) Slog.w(TAG, "All windows drawn on display " + displayId); + Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + finishWindowsDrawn(displayId); break; case MSG_HIDE_BOOT_MESSAGE: handleHideBootMessage(); @@ -3570,19 +3573,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { - if (mKeyguardDelegate != null && waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { + if (mKeyguardDelegate != null) { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; - } else { - setKeyguardOccludedLw(occluded); } } @Override public int applyKeyguardOcclusionChange() { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded=" - + mPendingKeyguardOccluded); + + mPendingKeyguardOccluded + " changed=" + mKeyguardOccludedChanged); // TODO(b/276433230): Explicitly save before/after for occlude state in each // Transition so we don't need to update SysUI every time. @@ -4998,15 +4999,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // ... eventually calls finishWindowsDrawn which will finalize our screen turn on // as well as enabling the orientation change logic/sensor. Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display"); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - INVALID_DISPLAY, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, INVALID_DISPLAY /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, INVALID_DISPLAY, 0), + WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); } // Called on the DisplayManager's DisplayPowerController thread. @@ -5086,15 +5082,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenOnListeners.put(displayId, screenOnListener); Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - displayId, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, displayId); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, displayId, 0), + WAITING_FOR_DRAWN_TIMEOUT, displayId); } } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 887f9461bdce..03a7bd3b68b3 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -169,7 +169,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * * @param occluded Whether Keyguard is currently occluded or not. */ - void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition); + void onKeyguardOccludedChangedLw(boolean occluded); /** * Commit any queued changes to keyguard occlude status that had been deferred during the diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index a53b831d55c1..d82f7a56a830 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -92,6 +92,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; +import android.provider.DeviceConfigInterface; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; @@ -128,6 +129,7 @@ import com.android.server.UiThread; import com.android.server.UserspaceRebootLogger; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.policy.WindowManagerPolicy; @@ -323,6 +325,9 @@ public final class PowerManagerService extends SystemService private final Injector mInjector; private final PermissionCheckerWrapper mPermissionCheckerWrapper; private final PowerPropertiesWrapper mPowerPropertiesWrapper; + private final DeviceConfigParameterProvider mDeviceConfigProvider; + + private boolean mDisableScreenWakeLocksWhileCached; private LightsManager mLightsManager; private BatteryManagerInternal mBatteryManagerInternal; @@ -1065,6 +1070,10 @@ public final class PowerManagerService extends SystemService } }; } + + DeviceConfigParameterProvider createDeviceConfigParameterProvider() { + return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); + } } /** Interface for checking an app op permission */ @@ -1161,6 +1170,7 @@ public final class PowerManagerService extends SystemService mInjector.createInattentiveSleepWarningController(); mPermissionCheckerWrapper = mInjector.createPermissionCheckerWrapper(); mPowerPropertiesWrapper = mInjector.createPowerPropertiesWrapper(); + mDeviceConfigProvider = mInjector.createDeviceConfigParameterProvider(); mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener(); @@ -1346,6 +1356,14 @@ public final class PowerManagerService extends SystemService mLightsManager = getLocalService(LightsManager.class); mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); + updateDeviceConfigLocked(); + mDeviceConfigProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(), + properties -> { + synchronized (mLock) { + updateDeviceConfigLocked(); + updateWakeLockDisabledStatesLocked(); + } + }); // Initialize display power management. mDisplayManagerInternal.initPowerManagement( @@ -1545,6 +1563,12 @@ public final class PowerManagerService extends SystemService updatePowerStateLocked(); } + @GuardedBy("mLock") + private void updateDeviceConfigLocked() { + mDisableScreenWakeLocksWhileCached = mDeviceConfigProvider + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + } + @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true) private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag, String packageName, WorkSource ws, String historyTag, int uid, int pid, @@ -2760,13 +2784,13 @@ public final class PowerManagerService extends SystemService /** Get wake lock summary flags that correspond to the given wake lock. */ @SuppressWarnings("deprecation") private int getWakeLockSummaryFlags(WakeLock wakeLock) { + if (wakeLock.mDisabled) { + // We only respect this if the wake lock is not disabled. + return 0; + } switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.PARTIAL_WAKE_LOCK: - if (!wakeLock.mDisabled) { - // We only respect this if the wake lock is not disabled. - return WAKE_LOCK_CPU; - } - break; + return WAKE_LOCK_CPU; case PowerManager.FULL_WAKE_LOCK: return WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT; case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: @@ -4151,7 +4175,7 @@ public final class PowerManagerService extends SystemService for (int i = 0; i < numWakeLocks; i++) { final WakeLock wakeLock = mWakeLocks.get(i); if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) - == PowerManager.PARTIAL_WAKE_LOCK) { + == PowerManager.PARTIAL_WAKE_LOCK || isScreenLock(wakeLock)) { if (setWakeLockDisabledStateLocked(wakeLock)) { changed = true; if (wakeLock.mDisabled) { @@ -4205,6 +4229,22 @@ public final class PowerManagerService extends SystemService } } return wakeLock.setDisabled(disabled); + } else if (mDisableScreenWakeLocksWhileCached && isScreenLock(wakeLock)) { + boolean disabled = false; + final int appid = UserHandle.getAppId(wakeLock.mOwnerUid); + final UidState state = wakeLock.mUidState; + // Cached inactive processes are never allowed to hold wake locks. + if (mConstants.NO_CACHED_WAKE_LOCKS + && appid >= Process.FIRST_APPLICATION_UID + && !state.mActive + && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT + && state.mProcState >= ActivityManager.PROCESS_STATE_TOP_SLEEPING) { + if (DEBUG_SPEW) { + Slog.d(TAG, "disabling full wakelock " + wakeLock); + } + disabled = true; + } + return wakeLock.setDisabled(disabled); } return false; } diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 4a57592aa1ae..27329e20bc8d 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -14613,17 +14613,13 @@ public class BatteryStatsImpl extends BatteryStats { // Inform StatsLog of setBatteryState changes. private void reportChangesToStatsLog(final int status, final int plugType, final int level) { - if (!mHaveBatteryLevel) { - return; - } - - if (mBatteryStatus != status) { + if (!mHaveBatteryLevel || mBatteryStatus != status) { FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status); } - if (mBatteryPlugType != plugType) { + if (!mHaveBatteryLevel || mBatteryPlugType != plugType) { FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType); } - if (mBatteryLevel != level) { + if (!mHaveBatteryLevel || mBatteryLevel != level) { FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level); } } diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b296ef2a1443..1ff01a6c70bf 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -1049,7 +1049,11 @@ public class VrManagerService extends SystemService for (ComponentName c : possibleServices) { if (Objects.equals(c.getPackageName(), pkg)) { - nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + try { + nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + } catch (Exception e) { + Slog.w(TAG, "Could not grant NLS access to package " + pkg, e); + } } } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 5af73726ddde..7a940ffc2913 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3095,7 +3095,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) { Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + " updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId) + && !isLockscreenLiveWallpaperEnabled()) { + which |= FLAG_LOCK; + } } wallpaper = getWallpaperSafeLocked(userId, which); @@ -3123,13 +3126,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void migrateStaticSystemToLockWallpaperLocked(int userId) { + private boolean migrateStaticSystemToLockWallpaperLocked(int userId) { WallpaperData sysWP = mWallpaperMap.get(userId); if (sysWP == null) { if (DEBUG) { Slog.i(TAG, "No system wallpaper? Not tracking for lock-only"); } - return; + return true; } // We know a-priori that there is no lock-only wallpaper currently @@ -3150,11 +3153,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub SELinux.restorecon(lockWP.wallpaperFile); mLastLockWallpaper = lockWP; } + return true; } catch (ErrnoException e) { - Slog.e(TAG, "Can't migrate system wallpaper: " + e.getMessage()); + // can happen when migrating default wallpaper (which is not stored in wallpaperFile) + Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage()); lockWP.wallpaperFile.delete(); lockWP.cropFile.delete(); - return; + return false; } } @@ -3355,7 +3360,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // therefore it's a shared system+lock image that we need to migrate. Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + "updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId)) { + which |= FLAG_LOCK; + } } } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index c5f63ced989c..a6d5c19395b0 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -285,9 +285,9 @@ class ActivityMetricsLogger { final LaunchingState mLaunchingState; /** The type can be cold (new process), warm (new activity), or hot (bring to front). */ - final int mTransitionType; + int mTransitionType; /** Whether the process was already running when the transition started. */ - final boolean mProcessRunning; + boolean mProcessRunning; /** whether the process of the launching activity didn't have any active activity. */ final boolean mProcessSwitch; /** The process state of the launching activity prior to the launch */ @@ -972,6 +972,19 @@ class ActivityMetricsLogger { // App isn't attached to record yet, so match with info. if (info.mLastLaunchedActivity.info.applicationInfo == appInfo) { info.mBindApplicationDelayMs = info.calculateCurrentDelay(); + if (info.mProcessRunning) { + // It was HOT/WARM launch, but the process was died somehow right after the + // launch request. + info.mProcessRunning = false; + info.mTransitionType = TYPE_TRANSITION_COLD_LAUNCH; + final String msg = "Process " + info.mLastLaunchedActivity.info.processName + + " restarted"; + Slog.i(TAG, msg); + if (info.mLaunchingState.mTraceName != null) { + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg + "#" + + LaunchingState.sTraceSeqId); + } + } } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 788bfbcd65c9..f6fa51e3018a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4516,7 +4516,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTransitionChangeFlags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; } // Post cleanup after the visibility and animation are transferred. - fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow); + fromActivity.postWindowRemoveStartingWindowCleanup(); fromActivity.mVisibleSetFromTransferredStartingWindow = false; mWmService.updateFocusedWindowLocked( @@ -7430,27 +7430,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - void postWindowRemoveStartingWindowCleanup(WindowState win) { - // TODO: Something smells about the code below...Is there a better way? - if (mStartingWindow == win) { - ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Notify removed startingWindow %s", win); - removeStartingWindow(); - } else if (mChildren.size() == 0) { - // If this is the last window and we had requested a starting transition window, - // well there is no point now. - ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Nulling last startingData"); - mStartingData = null; - if (mVisibleSetFromTransferredStartingWindow) { - // We set the visible state to true for the token from a transferred starting - // window. We now reset it back to false since the starting window was the last - // window in the token. - setVisible(false); - } - } else if (mChildren.size() == 1 && mStartingSurface != null && !isRelaunching()) { - // If this is the last window except for a starting transition window, - // we need to get rid of the starting transition. - ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Last window, removing starting window %s", win); - removeStartingWindow(); + void postWindowRemoveStartingWindowCleanup() { + if (mChildren.size() == 0 && mVisibleSetFromTransferredStartingWindow) { + // We set the visible state to true for the token from a transferred starting + // window. We now reset it back to false since the starting window was the last + // window in the token. + setVisible(false); } } @@ -7981,6 +7966,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastReportedConfiguration.getMergedConfiguration())) { ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */, false /* ignoreVisibility */, true /* isRequestedOrientationChanged */); + if (mTransitionController.inPlayingTransition(this)) { + mTransitionController.mValidateActivityCompat.add(this); + } } mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 0171c200b56c..b34ae1930048 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2186,7 +2186,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { * Processes the activities to be stopped or destroyed. This should be called when the resumed * activities are idle or drawn. */ - private void processStoppingAndFinishingActivities(ActivityRecord launchedActivity, + void processStoppingAndFinishingActivities(ActivityRecord launchedActivity, boolean processPausingActivities, String reason) { // Stop any activities that are scheduled to do so but have been waiting for the transition // animation to finish. @@ -2194,7 +2194,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { ArrayList<ActivityRecord> readyToStopActivities = null; for (int i = 0; i < mStoppingActivities.size(); i++) { final ActivityRecord s = mStoppingActivities.get(i); - final boolean animating = s.isInTransition(); + // Activity in a force hidden task should not be counted as animating, i.e., we want to + // send onStop before any configuration change when removing pip transition is ongoing. + final boolean animating = s.isInTransition() + && s.getTask() != null && !s.getTask().isForceHidden(); displaySwapping |= s.isDisplaySleepingAndSwapping(); ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b " + "finishing=%s", s, s.nowVisible, animating, s.finishing); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index b216578262b4..188f4d0b8ced 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -280,7 +280,7 @@ public class BackgroundActivityStartController { // visible window. if (Process.isSdkSandboxUid(realCallingUid)) { int realCallingSdkSandboxUidToAppUid = - Process.getAppUidForSdkSandboxUid(UserHandle.getAppId(realCallingUid)); + Process.getAppUidForSdkSandboxUid(realCallingUid); if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) { return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX, diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 89f044bdd163..d7667d8ce7a8 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -215,8 +215,7 @@ class Dimmer { return mDimState; } - private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, - float alpha, int blurRadius) { + private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { final DimState d = getDimState(container); if (d == null) { @@ -226,6 +225,7 @@ class Dimmer { // The dim method is called from WindowState.prepareSurfaces(), which is always called // in the correct Z from lowest Z to highest. This ensures that the dim layer is always // relative to the highest Z layer with a dim. + SurfaceControl.Transaction t = mHost.getPendingTransaction(); t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); t.setAlpha(d.mDimLayer, alpha); t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); @@ -238,26 +238,23 @@ class Dimmer { * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset * and the child should call dimAbove again to request the Dim to continue. * - * @param t A transaction in which to apply the Dim. * @param container The container which to dim above. Should be a child of our host. * @param alpha The alpha at which to Dim. */ - void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) { - dim(t, container, 1, alpha, 0); + void dimAbove(WindowContainer container, float alpha) { + dim(container, 1, alpha, 0); } /** * Like {@link #dimAbove} but places the dim below the given container. * - * @param t A transaction in which to apply the Dim. * @param container The container which to dim below. Should be a child of our host. * @param alpha The alpha at which to Dim. * @param blurRadius The amount of blur added to the Dim. */ - void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha, - int blurRadius) { - dim(t, container, -1, alpha, blurRadius); + void dimBelow(WindowContainer container, float alpha, int blurRadius) { + dim(container, -1, alpha, blurRadius); } /** diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 20936dcf5b42..d03388a8455e 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -419,13 +419,17 @@ class KeyguardController { return; } - final boolean waitAppTransition = isKeyguardLocked(displayId); - mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY), - waitAppTransition); - if (waitAppTransition) { - mService.deferWindowLayout(); - try { - if (isDisplayOccluded(DEFAULT_DISPLAY)) { + final TransitionController tc = mRootWindowContainer.mTransitionController; + + final boolean occluded = isDisplayOccluded(displayId); + final boolean performTransition = isKeyguardLocked(displayId); + final boolean executeTransition = performTransition && !tc.isCollecting(); + + mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded); + mService.deferWindowLayout(); + try { + if (isKeyguardLocked(displayId)) { + if (occluded) { mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_FLAG_KEYGUARD_OCCLUDING, @@ -435,11 +439,19 @@ class KeyguardController { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_FLAG_KEYGUARD_UNOCCLUDING); } - updateKeyguardSleepToken(DEFAULT_DISPLAY); + } else { + if (tc.inTransition()) { + tc.mStateValidators.add(mWindowManager.mPolicy::applyKeyguardOcclusionChange); + } else { + mWindowManager.mPolicy.applyKeyguardOcclusionChange(); + } + } + updateKeyguardSleepToken(displayId); + if (performTransition && executeTransition) { mWindowManager.executeAppTransition(); - } finally { - mService.continueWindowLayout(); } + } finally { + mService.continueWindowLayout(); } } @@ -486,6 +498,9 @@ class KeyguardController { } } + /** + * @return true if Keyguard is occluded or the device is dreaming. + */ boolean isDisplayOccluded(int displayId) { return getDisplayState(displayId).mOccluded; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 3d4df9d262de..9a0e47de7873 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1058,8 +1058,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> displayHasContent = true; } else if (displayContent != null && (!mObscureApplicationContentOnSecondaryDisplays + || displayContent.isKeyguardAlwaysUnlocked() || (obscured && w.mAttrs.type == TYPE_KEYGUARD_DIALOG))) { - // Allow full screen keyguard presentation dialogs to be seen. + // Allow full screen keyguard presentation dialogs to be seen, or simply ignore the + // keyguard if this display is always unlocked. displayHasContent = true; } if ((w.mAttrs.privateFlags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) { @@ -3206,6 +3208,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> + "not idle", rootTask.getRootTaskId(), resumedActivity); return false; } + if (mTransitionController.isTransientLaunch(resumedActivity)) { + // Not idle if the transient transition animation is running. + return false; + } } // End power mode launch when idle. mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 586077658a26..c914fa10687f 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -48,6 +48,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; import android.view.RemoteAnimationAdapter; +import android.window.RemoteTransition; import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; @@ -385,6 +386,18 @@ public class SafeActivityOptions { throw new SecurityException(msg); } + // Check permission for remote transitions + final RemoteTransition transition = options.getRemoteTransition(); + if (transition != null && supervisor.mService.checkPermission( + CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid) + != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + getIntentString(intent) + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with remoteTransition"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + // If launched from bubble is specified, then ensure that the caller is system or sysui. if (options.getLaunchedFromBubble() && !isSystemOrSystemUI(callingPid, callingUid)) { final String msg = "Permission Denial: starting " + getIntentString(intent) diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 5f2d3ca8175b..65c5c9b35ab7 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -27,6 +27,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; +import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -711,6 +712,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE; return; } + // Activity doesn't need to capture snapshot if the starting window has associated to task. + if (wc.asActivityRecord() != null) { + final ActivityRecord activityRecord = wc.asActivityRecord(); + if (activityRecord.mStartingData != null + && activityRecord.mStartingData.mAssociatedTask != null) { + return; + } + } if (mContainerFreezer == null) { mContainerFreezer = new ScreenshotFreezer(); @@ -1093,6 +1102,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final Task task = ar.getTask(); if (task == null) continue; boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); + // visibleAtTransitionEnd is used to guard against pre-maturely committing + // invisible on a window which is actually hidden by a later transition and not this + // one. However, for a transient launch, we can't use this mechanism because the + // visibility is determined at finish. Instead, use a different heuristic: don't + // commit invisible if the window is already in a later transition. That later + // transition will then handle the commit. + if (isTransientLaunch(ar) && !ar.isVisibleRequested() + && mController.inCollectingTransition(ar)) { + visibleAtTransitionEnd = true; + } // We need both the expected visibility AND current requested-visibility to be // false. If it is expected-visible but not currently visible, it means that // another animation is queued-up to animate this to invisibility, so we can't @@ -2649,7 +2668,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } private void validateKeyguardOcclusion() { - if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { + if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { mController.mStateValidators.add( mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange); } @@ -2666,7 +2685,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mStateValidators.add(() -> { for (int i = mTargets.size() - 1; i >= 0; --i) { final ChangeInfo change = mTargets.get(i); - if (!change.mContainer.isVisibleRequested()) continue; + if (!change.mContainer.isVisibleRequested() + || change.mContainer.mSurfaceControl == null) { + continue; + } Slog.e(TAG, "Force show for visible " + change.mContainer + " which may be hidden by transition unexpectedly"); change.mContainer.getSyncTransaction().show(change.mContainer.mSurfaceControl); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a539a4893d4f..b05c6b4839e5 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.WindowConfiguration; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; @@ -141,6 +142,14 @@ class TransitionController { final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>(); /** + * List of activity-level participants. ActivityRecord is not expected to change independently, + * however, recent compatibility logic can now cause this at arbitrary times determined by + * client code. If it happens during an animation, the surface can be left at the wrong spot. + * TODO(b/290237710) remove when compat logic is moved. + */ + final ArrayList<ActivityRecord> mValidateActivityCompat = new ArrayList<>(); + + /** * Currently playing transitions (in the order they were started). When finished, records are * removed from this list. */ @@ -905,6 +914,14 @@ class TransitionController { } } mValidateCommitVis.clear(); + for (int i = 0; i < mValidateActivityCompat.size(); ++i) { + ActivityRecord ar = mValidateActivityCompat.get(i); + if (ar.getSurfaceControl() == null) continue; + final Point tmpPos = new Point(); + ar.getRelativePosition(tmpPos); + ar.getSyncTransaction().setPosition(ar.getSurfaceControl(), tmpPos.x, tmpPos.y); + } + mValidateActivityCompat.clear(); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 9e7df004806b..805e7ffe7d76 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -30,6 +30,7 @@ import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.Bundle; import android.os.IBinder; +import android.os.Message; import android.util.Pair; import android.view.ContentRecordingSession; import android.view.Display; @@ -515,12 +516,13 @@ public abstract class WindowManagerInternal { * Invalidate all visible windows on a given display, and report back on the callback when all * windows have redrawn. * - * @param callback reporting callback to be called when all windows have redrawn. + * @param message The message will be sent when all windows have redrawn. Note that the message + * must be obtained from handler, otherwise it will throw NPE. * @param timeout calls the callback anyway after the timeout. * @param displayId waits for the windows on the given display, INVALID_DISPLAY to wait for all * windows on all displays. */ - public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId); + public abstract void waitForAllWindowsDrawn(Message message, long timeout, int displayId); /** * Overrides the display size. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 67572bfcad18..10687001f9a8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -601,7 +601,7 @@ public class WindowManagerService extends IWindowManager.Stub * The callbacks to make when the windows all have been drawn for a given * {@link WindowContainer}. */ - final HashMap<WindowContainer, Runnable> mWaitingForDrawnCallbacks = new HashMap<>(); + final ArrayMap<WindowContainer<?>, Message> mWaitingForDrawnCallbacks = new ArrayMap<>(); /** List of window currently causing non-system overlay windows to be hidden. */ private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>(); @@ -2020,7 +2020,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (win.mActivityRecord != null) { - win.mActivityRecord.postWindowRemoveStartingWindowCleanup(win); + win.mActivityRecord.postWindowRemoveStartingWindowCleanup(); } if (win.mAttrs.type == TYPE_WALLPAPER) { @@ -5368,8 +5368,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int NOTIFY_ACTIVITY_DRAWN = 32; - public static final int ALL_WINDOWS_DRAWN = 33; - public static final int NEW_ANIMATOR_SCALE = 34; public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36; @@ -5491,7 +5489,7 @@ public class WindowManagerService extends IWindowManager.Stub } case WAITING_FOR_DRAWN_TIMEOUT: { - Runnable callback = null; + final Message callback; final WindowContainer<?> container = (WindowContainer<?>) msg.obj; synchronized (mGlobalLock) { ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s", @@ -5505,7 +5503,7 @@ public class WindowManagerService extends IWindowManager.Stub callback = mWaitingForDrawnCallbacks.remove(container); } if (callback != null) { - callback.run(); + callback.sendToTarget(); } break; } @@ -5529,17 +5527,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case ALL_WINDOWS_DRAWN: { - Runnable callback; - final WindowContainer container = (WindowContainer) msg.obj; - synchronized (mGlobalLock) { - callback = mWaitingForDrawnCallbacks.remove(container); - } - if (callback != null) { - callback.run(); - } - break; - } case NEW_ANIMATOR_SCALE: { float scale = getCurrentAnimatorScale(); ValueAnimator.setDurationScale(scale); @@ -6097,7 +6084,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mWaitingForDrawnCallbacks.isEmpty()) { return; } - mWaitingForDrawnCallbacks.forEach((container, callback) -> { + for (int i = mWaitingForDrawnCallbacks.size() - 1; i >= 0; i--) { + final WindowContainer<?> container = mWaitingForDrawnCallbacks.keyAt(i); for (int j = container.mWaitingForDrawn.size() - 1; j >= 0; j--) { final WindowState win = (WindowState) container.mWaitingForDrawn.get(j); ProtoLog.i(WM_DEBUG_SCREEN_ON, @@ -6123,9 +6111,9 @@ public class WindowManagerService extends IWindowManager.Stub if (container.mWaitingForDrawn.isEmpty()) { ProtoLog.d(WM_DEBUG_SCREEN_ON, "All windows drawn!"); mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container); - mH.sendMessage(mH.obtainMessage(H.ALL_WINDOWS_DRAWN, container)); + mWaitingForDrawnCallbacks.removeAt(i).sendToTarget(); } - }); + } } private void traceStartWaitingForWindowDrawn(WindowState window) { @@ -7811,13 +7799,14 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) { + public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) { + Objects.requireNonNull(message.getTarget()); final WindowContainer<?> container = displayId == INVALID_DISPLAY ? mRoot : mRoot.getDisplayContent(displayId); if (container == null) { // The waiting container doesn't exist, no need to wait to run the callback. Run and // return; - callback.run(); + message.sendToTarget(); return; } boolean allWindowsDrawn = false; @@ -7834,13 +7823,13 @@ public class WindowManagerService extends IWindowManager.Stub } } - mWaitingForDrawnCallbacks.put(container, callback); + mWaitingForDrawnCallbacks.put(container, message); mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout); checkDrawnWindowsLocked(); } } if (allWindowsDrawn) { - callback.run(); + message.sendToTarget(); } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a2f7ba499fdf..027ab97693fd 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -559,6 +559,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (forceHiddenForPip) { wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); + // When removing pip, make sure that onStop is sent to the app ahead of + // onPictureInPictureModeChanged. + // See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss + wc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities( + null /* launchedActivity */, false /* processPausingActivities */, + "force-stop-on-removing-pip"); } int containerEffect = applyWindowContainerChange(wc, entry.getValue(), diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index cc9ac76a265e..06978a5338ea 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3389,12 +3389,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // apps won't always be considered as foreground state. // Exclude private presentations as they can only be shown on private virtual displays and // shouldn't be the cause of an app be considered foreground. - if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST - && mAttrs.type != TYPE_PRIVATE_PRESENTATION) { + // Exclude presentations on virtual displays as they are not actually visible. + if (mAttrs.type >= FIRST_SYSTEM_WINDOW + && mAttrs.type != TYPE_TOAST + && mAttrs.type != TYPE_PRIVATE_PRESENTATION + && !(mAttrs.type == TYPE_PRESENTATION && isOnVirtualDisplay()) + ) { mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown); } } + private boolean isOnVirtualDisplay() { + return getDisplayContent().mDisplay.getType() == Display.TYPE_VIRTUAL; + } + private void logExclusionRestrictions(int side) { if (!logsGestureExclusionRestrictions(this) || SystemClock.uptimeMillis() < mLastExclusionLogUptimeMillis[side] @@ -5120,7 +5128,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void applyDims() { if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind()) - && isVisibleNow() && !mHidden && mTransitionController.canApplyDim(getTask())) { + && mToken.isVisibleRequested() && isVisibleNow() && !mHidden + && mTransitionController.canApplyDim(getTask())) { // Only show the Dimmer when the following is satisfied: // 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested // 2. The WindowToken is not hidden so dims aren't shown when the window is exiting. @@ -5130,7 +5139,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mIsDimming = true; final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0; final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0; - getDimmer().dimBelow(getSyncTransaction(), this, dimAmount, blurRadius); + getDimmer().dimBelow(this, dimAmount, blurRadius); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index a1199d99f1c3..6747cea80115 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -91,8 +91,6 @@ public final class CredentialManagerService CredentialManagerService, CredentialManagerServiceImpl> { private static final String TAG = "CredManSysService"; - private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API = - "enable_credential_description_api"; private static final String PERMISSION_DENIED_ERROR = "permission_denied"; private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR = "Caller is missing WRITE_SECURE_SETTINGS permission"; @@ -311,14 +309,7 @@ public final class CredentialManagerService } public static boolean isCredentialDescriptionApiEnabled() { - final long origId = Binder.clearCallingIdentity(); - try { - return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, - false); - } finally { - Binder.restoreCallingIdentity(origId); - } + return true; } @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 46c90b4bc731..bafa4a56b28a 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -126,6 +126,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } protected ProviderRegistryGetSession(@NonNull Context context, @@ -143,6 +144,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } private List<Entry> prepareUiCredentialEntries( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 29275efc2ccb..34170fa4c8b5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -241,7 +241,6 @@ import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; import static android.provider.Telephony.Carriers.INVALID_APN_ID; import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION; - import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; @@ -540,6 +539,8 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -18809,10 +18810,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + ThreadPoolExecutor calculateHasIncompatibleAccountsExecutor = new ThreadPoolExecutor( + 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + @Override public void calculateHasIncompatibleAccounts() { + if (calculateHasIncompatibleAccountsExecutor.getQueue().size() > 1) { + return; + } new CalculateHasIncompatibleAccountsTask().executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR, null); + calculateHasIncompatibleAccountsExecutor, null); } @Nullable diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java index 24d0521471f7..0265453eecc7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java @@ -32,9 +32,8 @@ import java.util.Set; // TODO(scottjonathan): Replace with generic set implementation final class StringSetPolicySerializer extends PolicySerializer<Set<String>> { - private static final String ATTR_VALUES = ":strings"; + private static final String ATTR_VALUES = "strings"; private static final String ATTR_VALUES_SEPARATOR = ";"; - @Override void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Set<String> value) throws IOException { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 60e2af5f283c..c6a914b37206 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -68,7 +68,6 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.answer; @@ -2492,28 +2491,6 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test - public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() { - ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, - MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); - doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); - doReturn(app).when(sService).getTopApp(); - sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); - - assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); - - // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and - // verify that its OOM adjustment level is unaffected. - bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); - app.mServices.updateHasAboveClientLocked(); - assertFalse(app.mServices.hasAboveClient()); - - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); - assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); - } - - @SuppressWarnings("GuardedBy") - @Test public void testUpdateOomAdj_DoAll_Side_Cycle() { final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 931a2bf53b84..3c753326fb7d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -216,6 +216,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { val handler = TestHandler(null) val defaultAppProvider: DefaultAppProvider = mock() val backgroundHandler = TestHandler(null) + val updateOwnershipHelper: UpdateOwnershipHelper = mock() } companion object { @@ -303,6 +304,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.injector.handler) { mocks.handler } whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider } whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler } + whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper } wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig) whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP) whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST) diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java index d5ad815d3cdb..b5bf1ea34a46 100644 --- a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java @@ -16,7 +16,11 @@ package com.android.server.dreams; +import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; +import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; + import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; @@ -32,7 +36,9 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.IPowerManager; import android.os.IRemoteCallback; +import android.os.PowerManager; import android.os.RemoteException; import android.os.test.TestLooper; import android.service.dreams.IDreamService; @@ -58,6 +64,8 @@ public class DreamControllerTest { @Mock private ActivityTaskManager mActivityTaskManager; + @Mock + private IPowerManager mPowerManager; @Mock private IBinder mIBinder; @@ -67,6 +75,8 @@ public class DreamControllerTest { @Captor private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor; @Captor + private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor; + @Captor private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private final TestLooper mLooper = new TestLooper(); @@ -90,6 +100,12 @@ public class DreamControllerTest { when(mContext.getSystemServiceName(ActivityTaskManager.class)) .thenReturn(Context.ACTIVITY_TASK_SERVICE); + final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null); + when(mContext.getSystemService(Context.POWER_SERVICE)) + .thenReturn(powerManager); + when(mContext.getSystemServiceName(PowerManager.class)) + .thenReturn(Context.POWER_SERVICE); + mToken = new Binder(); mDreamName = ComponentName.unflattenFromString("dream"); mOverlayName = ComponentName.unflattenFromString("dream_overlay"); @@ -209,9 +225,51 @@ public class DreamControllerTest { verify(mIDreamService).detach(); } + @Test + public void serviceDisconnect_resetsScreenTimeout() throws RemoteException { + // Start dream. + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + ServiceConnection serviceConnection = captureServiceConnection(); + serviceConnection.onServiceConnected(mDreamName, mIBinder); + mLooper.dispatchAll(); + + // Dream disconnects unexpectedly. + serviceConnection.onServiceDisconnected(mDreamName); + mLooper.dispatchAll(); + + // Power manager receives user activity signal. + verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), + eq(USER_ACTIVITY_EVENT_OTHER), + eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); + } + + @Test + public void binderDied_resetsScreenTimeout() throws RemoteException { + // Start dream. + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + captureServiceConnection().onServiceConnected(mDreamName, mIBinder); + mLooper.dispatchAll(); + + // Dream binder dies. + captureDeathRecipient().binderDied(); + mLooper.dispatchAll(); + + // Power manager receives user activity signal. + verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), + eq(USER_ACTIVITY_EVENT_OTHER), + eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); + } + private ServiceConnection captureServiceConnection() { verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), any()); return mServiceConnectionACaptor.getValue(); } + + private IBinder.DeathRecipient captureDeathRecipient() throws RemoteException { + verify(mIBinder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); + return mDeathRecipientCaptor.getValue(); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 5c6164efb3b6..431d3a61de51 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -18,6 +18,8 @@ package com.android.server.power; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; +import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING; import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; @@ -78,6 +80,7 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.UserHandle; import android.os.test.TestLooper; +import android.provider.DeviceConfig; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.sysprop.PowerProperties; @@ -91,6 +94,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.PowerManagerService.BatteryReceiver; @@ -159,6 +163,7 @@ public class PowerManagerServiceTest { @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock; @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper; + @Mock private DeviceConfigParameterProvider mDeviceParameterProvider; @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); @@ -340,6 +345,11 @@ public class PowerManagerServiceTest { PowerManagerService.PowerPropertiesWrapper createPowerPropertiesWrapper() { return mPowerPropertiesWrapper; } + + @Override + DeviceConfigParameterProvider createDeviceConfigParameterProvider() { + return mDeviceParameterProvider; + } }); return mService; } @@ -2680,4 +2690,197 @@ public class PowerManagerServiceTest { verify(mNotifierMock, never()).onUserActivity(anyInt(), anyInt(), anyInt()); } + @Test + public void testFeatureEnabledProcStateUncachedToCached_fullWakeLockDisabled() { + doReturn(true).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK); + setUncachedUidProcState(wakeLock.mOwnerUid); + + setCachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isTrue(); + } + + @Test + public void testFeatureDisabledProcStateUncachedToCached_fullWakeLockEnabled() { + doReturn(false).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK); + setUncachedUidProcState(wakeLock.mOwnerUid); + + setCachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureEnabledProcStateUncachedToCached_screenBrightWakeLockDisabled() { + doReturn(true).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock", + PowerManager.SCREEN_BRIGHT_WAKE_LOCK); + setUncachedUidProcState(wakeLock.mOwnerUid); + + setCachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isTrue(); + } + + @Test + public void testFeatureDisabledProcStateUncachedToCached_screenBrightWakeLockEnabled() { + doReturn(false).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock", + PowerManager.SCREEN_BRIGHT_WAKE_LOCK); + setUncachedUidProcState(wakeLock.mOwnerUid); + + setCachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureEnabledProcStateUncachedToCached_screenDimWakeLockDisabled() { + doReturn(true).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenDimWakeLock", + PowerManager.SCREEN_DIM_WAKE_LOCK); + setUncachedUidProcState(wakeLock.mOwnerUid); + + setCachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isTrue(); + } + + @Test + public void testFeatureDisabledProcStateUncachedToCached_screenDimWakeLockEnabled() { + doReturn(false).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenDimWakeLock", + PowerManager.SCREEN_DIM_WAKE_LOCK); + setUncachedUidProcState(wakeLock.mOwnerUid); + + setCachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureEnabledProcStateCachedToUncached_fullWakeLockEnabled() { + doReturn(true).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK); + setCachedUidProcState(wakeLock.mOwnerUid); + + setUncachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureDisabledProcStateCachedToUncached_fullWakeLockEnabled() { + doReturn(false).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK); + setCachedUidProcState(wakeLock.mOwnerUid); + + setUncachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureEnabledProcStateCachedToUncached_screenBrightWakeLockEnabled() { + doReturn(true).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock", + PowerManager.SCREEN_BRIGHT_WAKE_LOCK); + setCachedUidProcState(wakeLock.mOwnerUid); + + setUncachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureDisabledProcStateCachedToUncached_screenBrightWakeLockEnabled() { + doReturn(false).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenBrightWakeLock", + PowerManager.SCREEN_BRIGHT_WAKE_LOCK); + setCachedUidProcState(wakeLock.mOwnerUid); + + setUncachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureEnabledProcStateCachedToUncached_screenDimWakeLockEnabled() { + doReturn(true).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenDimWakeLock", + PowerManager.SCREEN_DIM_WAKE_LOCK); + setCachedUidProcState(wakeLock.mOwnerUid); + + setUncachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureDisabledProcStateCachedToUncached_screenDimWakeLockEnabled() { + doReturn(false).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + createService(); + startSystem(); + WakeLock wakeLock = acquireWakeLock("screenDimWakeLock", + PowerManager.SCREEN_DIM_WAKE_LOCK); + setCachedUidProcState(wakeLock.mOwnerUid); + + setUncachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testFeatureDynamicallyDisabledProcStateUncachedToCached_fullWakeLockEnabled() { + doReturn(true).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> listenerCaptor = + ArgumentCaptor.forClass(DeviceConfig.OnPropertiesChangedListener.class); + createService(); + startSystem(); + verify(mDeviceParameterProvider, times(1)) + .addOnPropertiesChangedListener(any(), listenerCaptor.capture()); + WakeLock wakeLock = acquireWakeLock("fullWakeLock", PowerManager.FULL_WAKE_LOCK); + setUncachedUidProcState(wakeLock.mOwnerUid); + // dynamically disable the feature + doReturn(false).when(mDeviceParameterProvider) + .isDisableScreenWakeLocksWhileCachedFeatureEnabled(); + listenerCaptor.getValue().onPropertiesChanged( + new DeviceConfig.Properties("ignored_namespace", null)); + + setUncachedUidProcState(wakeLock.mOwnerUid); + assertThat(wakeLock.mDisabled).isFalse(); + } + + private void setCachedUidProcState(int uid) { + mService.updateUidProcStateInternal(uid, PROCESS_STATE_TOP_SLEEPING); + } + + private void setUncachedUidProcState(int uid) { + mService.updateUidProcStateInternal(uid, PROCESS_STATE_RECEIVER); + } } 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 cebc5409c795..4849665b0c44 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4148,6 +4148,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSetListenerAccessForUser_grantWithNameTooLong_throws() { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + assertThrows(IllegalArgumentException.class, + () -> mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ true, true)); + } + + @Test + public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exception { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ false, true); + + verify(mListeners).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true); + } + + @Test public void testSetAssistantAccessForUser() throws Exception { UserInfo ui = new UserInfo(); ui.id = mContext.getUserId() + 10; @@ -5879,6 +5903,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testVisitUris_wearableExtender() { + Icon actionIcon = Icon.createWithContentUri("content://media/action"); + Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction"); + PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build()) + .extend(new Notification.WearableExtender().addAction( + new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build())) + .build(); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor).accept(eq(actionIcon.getUri())); + verify(visitor).accept(eq(wearActionIcon.getUri())); + } + + @Test public void testSetNotificationPolicy_preP_setOldFields() { ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = mZenModeHelper; @@ -11166,7 +11210,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Given: a call notification has the flag FLAG_ONGOING_EVENT set // feature flag: ALLOW_DISMISS_ONGOING is on mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true); - when(mTelecomManager.isInManagedCall()).thenReturn(true); Person person = new Person.Builder() .setName("caller") diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java index beab107ec556..40f34a679c57 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java @@ -19,6 +19,15 @@ package com.android.server.notification; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE; +import static android.service.notification.NotificationStats.DISMISSAL_OTHER; + +import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_CLICK; +import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED; +import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_OTHER; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -155,4 +164,18 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { // Then: should return false assertFalse(NotificationRecordLogger.isNonDismissible(p.r)); } + + @Test + public void testBubbleGroupSummaryDismissal() { + assertEquals(NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED, + NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason( + REASON_GROUP_SUMMARY_CANCELED, DISMISSAL_BUBBLE)); + } + + @Test + public void testOtherNotificationCancel() { + assertEquals(NOTIFICATION_CANCEL_USER_OTHER, + NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason( + REASON_CANCEL, DISMISSAL_OTHER)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index 6668f8520820..024c38e5c1f7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -66,6 +66,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -87,7 +89,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // This list should be emptied! Items can be removed as bugs are fixed. private static final Multimap<Class<?>, String> KNOWN_BAD = ImmutableMultimap.<Class<?>, String>builder() - .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385 .put(Person.Builder.class, "setUri") // TODO: b/281044385 .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385 .build(); @@ -149,7 +150,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null, /* extenderClass= */ null, /* actionExtenderClass= */ null, /* includeRemoteViews= */ true); - assertThat(notification.includedUris.size()).isAtLeast(20); + assertThat(notification.includedUris.size()).isAtLeast(900); } @Test @@ -435,19 +436,43 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable) + " in " + where); - Class<?>[] parameterTypes = executable.getParameterTypes(); + Type[] parameterTypes = executable.getGenericParameterTypes(); Object[] parameterValues = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { - parameterValues[i] = generateObject( + parameterValues[i] = generateParameter( parameterTypes[i], where.plus(executable, - String.format("[%d,%s]", i, parameterTypes[i].getName())), + String.format("[%d,%s]", i, parameterTypes[i].getTypeName())), excludingClasses, specialGenerator); } return parameterValues; } + private static Object generateParameter(Type parameterType, Location where, + Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + if (parameterType instanceof Class<?> parameterClass) { + return generateObject( + parameterClass, + where, + excludingClasses, + specialGenerator); + } else if (parameterType instanceof ParameterizedType parameterizedType) { + if (parameterizedType.getRawType().equals(List.class) + && parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) { + ArrayList listValue = new ArrayList(); + for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) { + listValue.add( + generateObject((Class<?>) parameterizedType.getActualTypeArguments()[0], + where, excludingClasses, specialGenerator)); + } + return listValue; + } + } + throw new IllegalArgumentException( + "I have no idea how to produce a(n) " + parameterType + ", sorry"); + } + private static class ReflectionUtils { static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) { return Arrays.stream(containerClass.getDeclaredClasses()) diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 5c3102d870d0..65e77dcd4ca9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -182,12 +182,12 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { @Test public void testLaunchState() { - final ToIntFunction<Boolean> launchTemplate = doRelaunch -> { + final ToIntFunction<Runnable> launchTemplate = action -> { clearInvocations(mLaunchObserver); onActivityLaunched(mTopActivity); notifyTransitionStarting(mTopActivity); - if (doRelaunch) { - mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity); + if (action != null) { + action.run(); } final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity); @@ -199,21 +199,27 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { // Assume that the process is started (ActivityBuilder has mocked the returned value of // ATMS#getProcessController) but the activity has not attached process. mTopActivity.app = null; - assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_WARM); mTopActivity.app = app; mNewActivityCreated = false; - assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_HOT); - assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */)) + assertWithMessage("Relaunch").that(launchTemplate.applyAsInt( + () -> mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity))) .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH); + assertWithMessage("Cold launch by restart").that(launchTemplate.applyAsInt( + () -> mActivityMetricsLogger.notifyBindApplication( + mTopActivity.info.applicationInfo))) + .isEqualTo(WaitResult.LAUNCH_STATE_COLD); + mTopActivity.app = null; mNewActivityCreated = true; doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid); - assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */)) + assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(null)) .isEqualTo(WaitResult.LAUNCH_STATE_COLD); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 2b589bf59682..9842d7009444 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -74,7 +74,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -103,6 +102,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.service.voice.IVoiceInteractionSession; @@ -159,6 +159,9 @@ public class ActivityStarterTests extends WindowTestsBase { private static final String FAKE_CALLING_PACKAGE = "com.whatever.dude"; private static final int UNIMPORTANT_UID = 12345; private static final int UNIMPORTANT_UID2 = 12346; + private static final int SDK_SANDBOX_UID = Process.toSdkSandboxUid(UNIMPORTANT_UID); + private static final int SECONDARY_USER_SDK_SANDBOX_UID = + UserHandle.getUid(10, SDK_SANDBOX_UID); private static final int CURRENT_IME_UID = 12347; protected final DeviceConfigStateHelper mDeviceConfig = new DeviceConfigStateHelper( @@ -958,6 +961,48 @@ public class ActivityStarterTests extends WindowTestsBase { mockingSession.finishMocking(); } + + @Test + public void testBackgroundActivityStartsAllowed_sdkSandboxClientAppHasVisibleWindow() { + doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled(); + // The SDK's associated client app has a visible window + doReturn(true).when(mAtm).hasActiveVisibleWindow( + Process.getAppUidForSdkSandboxUid(SDK_SANDBOX_UID)); + runAndVerifyBackgroundActivityStartsSubtest( + "allowed_sdkSandboxClientAppHasVisibleWindow", false, SDK_SANDBOX_UID, + false, PROCESS_STATE_TOP, SDK_SANDBOX_UID, false, + PROCESS_STATE_TOP, true, false, false, + false, false, false, false, false); + } + + @Test + public void testBackgroundActivityStartsDisallowed_sdkSandboxClientHasNoVisibleWindow() { + doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled(); + // The SDK's associated client app does not have a visible window + doReturn(false).when(mAtm).hasActiveVisibleWindow( + Process.getAppUidForSdkSandboxUid(SDK_SANDBOX_UID)); + runAndVerifyBackgroundActivityStartsSubtest( + "disallowed_sdkSandboxClientHasNoVisibleWindow", true, SDK_SANDBOX_UID, + false, PROCESS_STATE_TOP, SDK_SANDBOX_UID, false, + PROCESS_STATE_TOP, true, false, false, + false, false, false, false, false); + + } + + @Test + public void testBackgroundActivityStartsAllowed_sdkSandboxMultiUserClientHasVisibleWindow() { + doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled(); + // The SDK's associated client app has a visible window + doReturn(true).when(mAtm).hasActiveVisibleWindow( + Process.getAppUidForSdkSandboxUid(SECONDARY_USER_SDK_SANDBOX_UID)); + runAndVerifyBackgroundActivityStartsSubtest( + "allowed_sdkSandboxMultiUserClientHasVisibleWindow", false, + SECONDARY_USER_SDK_SANDBOX_UID, false, PROCESS_STATE_TOP, + SECONDARY_USER_SDK_SANDBOX_UID, false, PROCESS_STATE_TOP, + false, false, false, false, + false, false, false, false); + } + private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted, int callingUid, boolean callingUidHasVisibleWindow, int callingUidProcState, int realCallingUid, boolean realCallingUidHasVisibleWindow, int realCallingUidProcState, diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index f235d153c658..233a2076a867 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -52,7 +52,8 @@ public class DimmerTests extends WindowTestsBase { private static class TestWindowContainer extends WindowContainer<TestWindowContainer> { final SurfaceControl mControl = mock(SurfaceControl.class); - final SurfaceControl.Transaction mTransaction = spy(StubTransaction.class); + final SurfaceControl.Transaction mPendingTransaction = spy(StubTransaction.class); + final SurfaceControl.Transaction mSyncTransaction = spy(StubTransaction.class); TestWindowContainer(WindowManagerService wm) { super(wm); @@ -65,12 +66,12 @@ public class DimmerTests extends WindowTestsBase { @Override public SurfaceControl.Transaction getSyncTransaction() { - return mTransaction; + return mSyncTransaction; } @Override public SurfaceControl.Transaction getPendingTransaction() { - return mTransaction; + return mPendingTransaction; } } @@ -144,7 +145,7 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(child, 0); final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); + mDimmer.dimAbove(child, alpha); int width = 100; int height = 300; @@ -161,13 +162,13 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(child, 0); final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); + mDimmer.dimAbove(child, alpha); SurfaceControl dimLayer = getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); - verify(mTransaction).setAlpha(dimLayer, alpha); - verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, 1); + verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1); } @Test @@ -176,13 +177,13 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(child, 0); final float alpha = 0.8f; - mDimmer.dimBelow(mTransaction, child, alpha, 0); + mDimmer.dimBelow(child, alpha, 0); SurfaceControl dimLayer = getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); - verify(mTransaction).setAlpha(dimLayer, alpha); - verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, -1); + verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); } @Test @@ -191,7 +192,7 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(child, 0); final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); + mDimmer.dimAbove(child, alpha); SurfaceControl dimLayer = getDimLayer(); mDimmer.resetDimStates(); @@ -208,10 +209,10 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(child, 0); final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); + mDimmer.dimAbove(child, alpha); SurfaceControl dimLayer = getDimLayer(); mDimmer.resetDimStates(); - mDimmer.dimAbove(mTransaction, child, alpha); + mDimmer.dimAbove(child, alpha); mDimmer.updateDims(mTransaction); verify(mTransaction).show(dimLayer); @@ -224,7 +225,7 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(child, 0); final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); + mDimmer.dimAbove(child, alpha); final Rect bounds = mDimmer.mDimState.mDimBounds; SurfaceControl dimLayer = getDimLayer(); @@ -245,7 +246,7 @@ public class DimmerTests extends WindowTestsBase { TestWindowContainer child = new TestWindowContainer(mWm); mHost.addChild(child, 0); - mDimmer.dimAbove(mTransaction, child, 1); + mDimmer.dimAbove(child, 1); SurfaceControl dimLayer = getDimLayer(); mDimmer.updateDims(mTransaction); verify(mTransaction, times(1)).show(dimLayer); @@ -266,13 +267,13 @@ public class DimmerTests extends WindowTestsBase { mHost.addChild(child, 0); final int blurRadius = 50; - mDimmer.dimBelow(mTransaction, child, 0, blurRadius); + mDimmer.dimBelow(child, 0, blurRadius); SurfaceControl dimLayer = getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); - verify(mTransaction).setBackgroundBlurRadius(dimLayer, blurRadius); - verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, -1); + verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius); + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); } private SurfaceControl getDimLayer() { diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index adf3f3976f38..bd111ada8550 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -233,7 +233,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { } public void setSafeMode(boolean safeMode) { 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 ed0c8ef489e5..d4fabf40d5d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -61,6 +61,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -1425,6 +1426,15 @@ public class TransitionTests extends WindowTestsBase { // No need to wait for the activity in transient hide task. assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState); + // An active transient launch overrides idle state to avoid clearing power mode before the + // transition is finished. + spyOn(mRootWindowContainer.mTransitionController); + doAnswer(invocation -> controller.isTransientLaunch(invocation.getArgument(0))).when( + mRootWindowContainer.mTransitionController).isTransientLaunch(any()); + activity2.getTask().setResumedActivity(activity2, "test"); + activity2.idle = true; + assertFalse(mRootWindowContainer.allResumedActivitiesIdle()); + activity1.setVisibleRequested(false); activity2.setVisibleRequested(true); activity2.setVisible(true); diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java index 7fe8582f96de..c508fa968b14 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -59,6 +59,8 @@ public final class UsbAlsaDevice { private String mDeviceName = ""; private String mDeviceDescription = ""; + private boolean mHasJackDetect = true; + public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, boolean hasOutput, boolean hasInput, boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) { @@ -168,8 +170,14 @@ public final class UsbAlsaDevice { if (mJackDetector != null) { return; } + if (!mHasJackDetect) { + return; + } // If no jack detect capabilities exist, mJackDetector will be null. mJackDetector = UsbAlsaJackDetector.startJackDetect(this); + if (mJackDetector == null) { + mHasJackDetect = false; + } } /** Stops a jack-detection thread. */ @@ -182,8 +190,8 @@ public final class UsbAlsaDevice { /** Start using this device as the selected USB Audio Device. */ public synchronized void start() { - startInput(); startOutput(); + startInput(); } /** Start using this device as the selected USB input device. */ @@ -208,8 +216,8 @@ public final class UsbAlsaDevice { /** Stop using this device as the selected USB Audio Device. */ public synchronized void stop() { - stopInput(); stopOutput(); + stopInput(); } /** Stop using this device as the selected USB input device. */ diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java index db369756c50e..346622f0f467 100644 --- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java +++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java @@ -61,8 +61,11 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { options.setTestMethodName("testCollectAllApexInfo"); // Collect APEX package names from /apex, then pass them as expectation to be verified. + // The package names are collected from the find name with deduplication (NB: we used to + // deduplicate by dropping directory names with '@', but there's a DCLA case where it only + // has one directory with '@'. So we have to keep it and deduplicate the current way). CommandResult result = getDevice().executeShellV2Command( - "ls -d /apex/*/ |grep -v @ |grep -v /apex/sharedlibs |cut -d/ -f3"); + "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq"); assertTrue(result.getStatus() == CommandStatus.SUCCESS); String[] packageNames = result.getStdout().split("\n"); for (var i = 0; i < packageNames.length; i++) { diff --git a/tests/Internal/src/android/service/wallpaper/OWNERS b/tests/Internal/src/android/service/wallpaper/OWNERS new file mode 100644 index 000000000000..5a26d0e1f62b --- /dev/null +++ b/tests/Internal/src/android/service/wallpaper/OWNERS @@ -0,0 +1,4 @@ +dupin@google.com +santie@google.com +pomini@google.com +poultney@google.com
\ No newline at end of file diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java index 153ca79e346b..0c5e8d481131 100644 --- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java +++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java @@ -85,4 +85,17 @@ public class WallpaperServiceTest { assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]); } + @Test + public void testNotifyColorsOfDestroyedEngine_doesntCrash() { + WallpaperService service = new WallpaperService() { + @Override + public Engine onCreateEngine() { + return new Engine(); + } + }; + WallpaperService.Engine engine = service.onCreateEngine(); + engine.detach(); + + engine.notifyColorsChanged(); + } } |