Merge "[Output Switcher] Set padding to fix clip on top item" into udc-dev
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a6313db..776e34b 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -571,6 +571,15 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L;
+ /**
+ * Media controls based on {@link android.app.Notification.MediaStyle} notifications will be
+ * required to include a non-empty title, either in the {@link android.media.MediaMetadata} or
+ * notification title.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L;
+
@UnsupportedAppUsage
private Context mContext;
private IStatusBarService mService;
@@ -1217,6 +1226,21 @@
}
/**
+ * Checks whether the given package must include a non-empty title for its media controls.
+ *
+ * @param packageName App posting media controls
+ * @param user Current user handle
+ * @return true if the app is required to provide a non-empty title
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ android.Manifest.permission.LOG_COMPAT_CHANGE})
+ public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) {
+ return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user);
+ }
+
+ /**
* Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
* a system activity that captures content on the screen to take a screenshot.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e9fb811..59b5978 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9862,6 +9862,9 @@
* profile owner of an organization-owned managed profile.
* @throws IllegalArgumentException if called on the parent profile and the package
* provided is not a pre-installed system package.
+ * @throws IllegalStateException while trying to set default sms app on the profile and
+ * {@link ManagedSubscriptionsPolicy#TYPE_ALL_MANAGED_SUBSCRIPTIONS}
+ * policy is not set.
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_DEFAULT_SMS, conditional = true)
public void setDefaultSmsApplication(@Nullable ComponentName admin,
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 7ac8f37..c3df17d 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -27,6 +27,7 @@
import android.annotation.TestApi;
import android.annotation.UserHandleAware;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.AppOpsManager.Mode;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -110,8 +111,8 @@
component,
targetUser.getIdentifier(),
true,
- null,
- null);
+ mContext.getActivityToken(),
+ ActivityOptions.makeBasic().toBundle());
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a42af1a..fd5e206 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12446,6 +12446,17 @@
"bypass_device_policy_management_role_qualifications";
/**
+ * Whether work profile telephony feature is enabled for non
+ * {@link android.app.role.RoleManager#ROLE_DEVICE_POLICY_MANAGEMENT} holders.
+ * ("0" = false, "1" = true).
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS =
+ "allow_work_profile_telephony_for_non_dpm_role_holders";
+
+ /**
* Indicates whether mobile data should be allowed while the device is being provisioned.
* This allows the provisioning process to turn off mobile data before the user
* has an opportunity to set things up, preventing other processes from burning
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 54d19eb..ee6ac12 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -50,6 +50,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -205,7 +206,7 @@
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
- tn.mNextView = mNextView;
+ tn.mNextView = new WeakReference<>(mNextView);
final boolean isUiContext = mContext.isUiContext();
final int displayId = mContext.getDisplayId();
@@ -622,7 +623,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
View mView;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- View mNextView;
+ WeakReference<View> mNextView;
int mDuration;
WindowManager mWM;
@@ -632,7 +633,7 @@
private final ToastPresenter mPresenter;
@GuardedBy("mCallbacks")
- private final List<Callback> mCallbacks;
+ private final WeakReference<List<Callback>> mCallbacks;
/**
* Creates a {@link ITransientNotification} object.
@@ -649,7 +650,7 @@
mParams = mPresenter.getLayoutParams();
mPackageName = packageName;
mToken = token;
- mCallbacks = callbacks;
+ mCallbacks = new WeakReference<>(callbacks);
mHandler = new Handler(looper, null) {
@Override
@@ -685,7 +686,11 @@
private List<Callback> getCallbacks() {
synchronized (mCallbacks) {
- return new ArrayList<>(mCallbacks);
+ if (mCallbacks.get() != null) {
+ return new ArrayList<>(mCallbacks.get());
+ } else {
+ return new ArrayList<>();
+ }
}
}
@@ -721,13 +726,15 @@
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
- if (mView != mNextView) {
+ if (mNextView != null && mView != mNextView.get()) {
// remove the old view if necessary
handleHide();
- mView = mNextView;
- mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
- mHorizontalMargin, mVerticalMargin,
- new CallbackBinder(getCallbacks(), mHandler));
+ mView = mNextView.get();
+ if (mView != null) {
+ mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
+ mHorizontalMargin, mVerticalMargin,
+ new CallbackBinder(getCallbacks(), mHandler));
+ }
}
}
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 7467100..7cb61fe 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -41,6 +41,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import java.lang.ref.WeakReference;
+
/**
* Class responsible for toast presentation inside app's process and in system UI.
*
@@ -87,25 +89,33 @@
return view;
}
- private final Context mContext;
private final Resources mResources;
- private final WindowManager mWindowManager;
- private final IAccessibilityManager mAccessibilityManager;
+ private final WeakReference<WindowManager> mWindowManager;
+ private final WeakReference<AccessibilityManager> mAccessibilityManager;
private final INotificationManager mNotificationManager;
private final String mPackageName;
+ private final String mContextPackageName;
private final WindowManager.LayoutParams mParams;
@Nullable private View mView;
@Nullable private IBinder mToken;
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
INotificationManager notificationManager, String packageName) {
- mContext = context;
mResources = context.getResources();
- mWindowManager = context.getSystemService(WindowManager.class);
+ mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
mNotificationManager = notificationManager;
mPackageName = packageName;
- mAccessibilityManager = accessibilityManager;
+ mContextPackageName = context.getPackageName();
mParams = createLayoutParams();
+
+ // We obtain AccessibilityManager manually via its constructor instead of using method
+ // AccessibilityManager.getInstance() for 2 reasons:
+ // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
+ // 2. getInstance() caches the instance for the process even if we pass a different
+ // context to it. This is problematic for multi-user because callers can pass a context
+ // created via Context.createContextAsUser().
+ mAccessibilityManager = new WeakReference<>(
+ new AccessibilityManager(context, accessibilityManager, context.getUserId()));
}
public String getPackageName() {
@@ -173,7 +183,7 @@
params.y = yOffset;
params.horizontalMargin = horizontalMargin;
params.verticalMargin = verticalMargin;
- params.packageName = mContext.getPackageName();
+ params.packageName = mContextPackageName;
params.hideTimeoutMilliseconds =
(duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
params.token = windowToken;
@@ -270,8 +280,9 @@
public void hide(@Nullable ITransientNotificationCallback callback) {
checkState(mView != null, "No toast to hide.");
- if (mView.getParent() != null) {
- mWindowManager.removeViewImmediate(mView);
+ final WindowManager windowManager = mWindowManager.get();
+ if (mView.getParent() != null && windowManager != null) {
+ windowManager.removeViewImmediate(mView);
}
try {
mNotificationManager.finishToken(mPackageName, mToken);
@@ -295,14 +306,11 @@
* enabled.
*/
public void trySendAccessibilityEvent(View view, String packageName) {
- // We obtain AccessibilityManager manually via its constructor instead of using method
- // AccessibilityManager.getInstance() for 2 reasons:
- // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
- // 2. getInstance() caches the instance for the process even if we pass a different
- // context to it. This is problematic for multi-user because callers can pass a context
- // created via Context.createContextAsUser().
- final AccessibilityManager accessibilityManager =
- new AccessibilityManager(mContext, mAccessibilityManager, mContext.getUserId());
+ final AccessibilityManager accessibilityManager = mAccessibilityManager.get();
+ if (accessibilityManager == null) {
+ return;
+ }
+
if (!accessibilityManager.isEnabled()) {
accessibilityManager.removeClient();
return;
@@ -320,11 +328,15 @@
}
private void addToastView() {
+ final WindowManager windowManager = mWindowManager.get();
+ if (windowManager == null) {
+ return;
+ }
if (mView.getParent() != null) {
- mWindowManager.removeView(mView);
+ windowManager.removeView(mView);
}
try {
- mWindowManager.addView(mView, mParams);
+ windowManager.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
// Since the notification manager service cancels the token right after it notifies us
// to cancel the toast there is an inherent race and we may attempt to add a window
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 618670a..e58c044 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -43,7 +43,7 @@
private ProgressCallback mCallback;
private float mProgress = 0;
private BackMotionEvent mLastBackEvent;
- private boolean mStarted = false;
+ private boolean mBackAnimationInProgress = false;
private void setProgress(float progress) {
mProgress = progress;
@@ -87,7 +87,7 @@
* @param event the {@link BackMotionEvent} containing the latest target progress.
*/
public void onBackProgressed(BackMotionEvent event) {
- if (!mStarted) {
+ if (!mBackAnimationInProgress) {
return;
}
mLastBackEvent = event;
@@ -108,7 +108,7 @@
reset();
mLastBackEvent = event;
mCallback = callback;
- mStarted = true;
+ mBackAnimationInProgress = true;
}
/**
@@ -122,7 +122,7 @@
// Should never happen.
mSpring.cancel();
}
- mStarted = false;
+ mBackAnimationInProgress = false;
mLastBackEvent = null;
mCallback = null;
mProgress = 0;
@@ -149,8 +149,13 @@
mSpring.animateToFinalPosition(0);
}
+ /** Returns true if the back animation is in progress. */
+ boolean isBackAnimationInProgress() {
+ return mBackAnimationInProgress;
+ }
+
private void updateProgressValue(float progress) {
- if (mLastBackEvent == null || mCallback == null || !mStarted) {
+ if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) {
return;
}
mCallback.onProgressUpdate(
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 4d0132e..22b2ec0 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -158,6 +158,16 @@
mAllCallbacks.remove(callback);
// Re-populate the top callback to WM if the removed callback was previously the top one.
if (previousTopCallback == callback) {
+ // We should call onBackCancelled() when an active callback is removed from dispatcher.
+ if (mProgressAnimator.isBackAnimationInProgress()
+ && callback instanceof OnBackAnimationCallback) {
+ // The ProgressAnimator will handle the new topCallback, so we don't want to call
+ // onBackCancelled() on it. We call immediately the callback instead.
+ OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
+ animatedCallback.onBackCancelled();
+ Log.d(TAG, "The callback was removed while a back animation was in progress, "
+ + "an onBackCancelled() was dispatched.");
+ }
setTopOnBackInvokedCallback(getTopCallback());
}
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index db65cb3..7452daa 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -33,6 +33,7 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
@@ -230,7 +231,15 @@
((Button) findViewById(R.id.button_open)).setText(getOpenInWorkButtonString(launchIntent));
findViewById(R.id.button_open).setOnClickListener(v -> {
- startActivityAsCaller(launchIntent, targetUserId);
+ startActivityAsCaller(
+ launchIntent,
+ ActivityOptions.makeCustomAnimation(
+ getApplicationContext(),
+ R.anim.activity_open_enter,
+ R.anim.push_down_out)
+ .toBundle(),
+ /* ignoreTargetSecurity= */ false,
+ targetUserId);
finish();
});
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index af08e03..63afc34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -863,6 +863,7 @@
android:label="@string/permlab_readContacts"
android:description="@string/permdesc_readContacts"
android:protectionLevel="dangerous" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- Allows an application to write the user's contacts data.
<p>Protection level: dangerous
@@ -6039,6 +6040,7 @@
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.CALL_PRIVILEGED"
android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<!-- @SystemApi Allows an application to perform CDMA OTA provisioning @hide -->
<permission android:name="android.permission.PERFORM_CDMA_PROVISIONING"
@@ -8231,7 +8233,7 @@
</service>
<service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
- android:permission="android.permission.BIND_CONNECTION_SERVICE"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.ConnectionService"/>
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index 2e65800..d1a4935 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -35,10 +35,10 @@
<ImageView
android:id="@+id/autofill_service_icon"
- android:scaleType="fitStart"
+ android:scaleType="fitCenter"
android:visibility="gone"
- android:layout_width="@dimen/autofill_dialog_icon_size"
- android:layout_height="@dimen/autofill_dialog_icon_size"/>
+ android:layout_height="@dimen/autofill_dialog_icon_max_height"
+ android:layout_width="fill_parent"/>
<LinearLayout
android:id="@+id/autofill_dialog_header"
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 3c0b789..85529d6 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -40,10 +40,10 @@
<ImageView
android:id="@+id/autofill_save_icon"
- android:scaleType="fitStart"
+ android:scaleType="fitCenter"
android:layout_gravity="center"
- android:layout_width="@dimen/autofill_save_icon_size"
- android:layout_height="@dimen/autofill_save_icon_size"/>
+ android:layout_height="@dimen/autofill_save_icon_max_height"
+ android:layout_width="fill_parent"/>
<TextView
android:id="@+id/autofill_save_title"
@@ -60,7 +60,6 @@
android:id="@+id/autofill_save_custom_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/autofill_save_scroll_view_top_margin"
android:visibility="gone"/>
</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8899785..00f8db0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3718,6 +3718,10 @@
magnification settings and adjust the default magnification capability. -->
<bool name="config_magnification_area">true</bool>
+ <!-- The default value for always on magnification feature flag if the remote feature
+ flag does not exist -->
+ <bool name="config_magnification_always_on_enabled">true</bool>
+
<!-- If true, the display will be shifted around in ambient mode. -->
<bool name="config_enableBurnInProtection">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bc0af12..24da59a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -874,6 +874,7 @@
<dimen name="autofill_elevation">32dp</dimen>
<dimen name="autofill_save_inner_padding">16dp</dimen>
<dimen name="autofill_save_icon_size">32dp</dimen>
+ <dimen name="autofill_save_icon_max_height">56dp</dimen>
<dimen name="autofill_save_title_start_padding">8dp</dimen>
<dimen name="autofill_save_scroll_view_top_margin">16dp</dimen>
<dimen name="autofill_save_button_bar_padding">16dp</dimen>
@@ -882,19 +883,18 @@
<!-- Max height of the the autofill save custom subtitle as a fraction of the screen width/height -->
<dimen name="autofill_save_custom_subtitle_max_height">20%</dimen>
- <!-- Max (absolute) dimensions (both width and height) of autofill service icon on autofill save affordance.
- NOTE: the actual displayed size might is actually smaller than this and is hardcoded in the
- autofill_save.xml layout; this dimension is just used to avoid a crash in the UI (if the icon provided
- by the autofill service metadata is bigger than these dimentionsit will not be displayed).
- -->
- <dimen name="autofill_save_icon_max_size">300dp</dimen>
-
<!-- Maximum number of datasets that are visible in the UX picker without scrolling -->
<integer name="autofill_max_visible_datasets">3</integer>
- <!-- Size of an icon in the Autolfill fill dialog -->
+ <!-- Size of an icon in the Autofill fill dialog -->
<dimen name="autofill_dialog_icon_size">32dp</dimen>
+ <!-- The max height of an icon in the Autofill fill dialog. -->
+ <dimen name="autofill_dialog_icon_max_height">56dp</dimen>
+
+ <!-- The max width of the Autofill fill dialog. -->
+ <dimen name="autofill_dialog_max_width">640dp</dimen>
+
<!-- Size of a slice shortcut view -->
<dimen name="slice_shortcut_size">56dp</dimen>
<!-- Size of action icons in a slice -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1e9e8dd..f35e32b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3660,8 +3660,8 @@
<java-symbol type="dimen" name="autofill_dataset_picker_max_width"/>
<java-symbol type="dimen" name="autofill_dataset_picker_max_height"/>
<java-symbol type="dimen" name="autofill_save_custom_subtitle_max_height"/>
- <java-symbol type="dimen" name="autofill_save_icon_max_size"/>
<java-symbol type="integer" name="autofill_max_visible_datasets" />
+ <java-symbol type="dimen" name="autofill_dialog_max_width" />
<java-symbol type="style" name="Theme.DeviceDefault.Autofill" />
<java-symbol type="style" name="Theme.DeviceDefault.Light.Autofill" />
@@ -4536,6 +4536,7 @@
<java-symbol type="string" name="dismiss_action" />
<java-symbol type="bool" name="config_magnification_area" />
+ <java-symbol type="bool" name="config_magnification_always_on_enabled" />
<java-symbol type="bool" name="config_trackerAppNeedsPermissions"/>
<!-- FullScreenMagnification thumbnail -->
@@ -5017,7 +5018,7 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
-
+
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
<java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index cde100c..8e772a2 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -173,4 +173,25 @@
waitForIdle();
verify(mCallback2).onBackStarted(any(BackEvent.class));
}
+
+ @Test
+ public void onUnregisterWhileBackInProgress_callOnBackCancelled() throws RemoteException {
+ ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
+ ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+
+ mDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+
+ verify(mWindowSession).setOnBackInvokedCallbackInfo(
+ Mockito.eq(mWindow),
+ captor.capture());
+ IOnBackInvokedCallback iOnBackInvokedCallback = captor.getValue().getCallback();
+ iOnBackInvokedCallback.onBackStarted(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
+
+ mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
+ verify(mCallback1).onBackCancelled();
+ verifyNoMoreInteractions(mCallback1);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index ff423c2..96190c4b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -31,12 +31,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.WindowExtensions;
import androidx.window.extensions.core.util.function.Consumer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -51,6 +53,7 @@
public class WindowAreaComponentImpl implements WindowAreaComponent,
DeviceStateManager.DeviceStateCallback {
+ private static final int INVALID_DISPLAY_ADDRESS = -1;
private final Object mLock = new Object();
@NonNull
@@ -69,8 +72,7 @@
private final int mConcurrentDisplayState;
@NonNull
private final int[] mFoldedDeviceStates;
- @NonNull
- private long mRearDisplayAddress = 0;
+ private long mRearDisplayAddress = INVALID_DISPLAY_ADDRESS;
@WindowAreaSessionState
private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
@@ -109,10 +111,7 @@
R.integer.config_deviceStateConcurrentRearDisplay);
mDeviceStateManager.registerCallback(mExecutor, this);
- if (mConcurrentDisplayState != INVALID_DEVICE_STATE) {
- mRearDisplayAddress = Long.parseLong(context.getResources().getString(
- R.string.config_rearDisplayPhysicalAddress));
- }
+ mRearDisplayAddress = getRearDisplayAddress(context);
}
/**
@@ -220,6 +219,44 @@
}
/**
+ * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing
+ * display was not found in the display list, but we have already computed the
+ * {@link DisplayMetrics} for that display, we return the cached value. If no display has been
+ * found, then we return an empty {@link DisplayMetrics} value.
+ *
+ * TODO(b/267563768): Update with guidance from Display team for missing displays.
+ *
+ * @since {@link WindowExtensions#VENDOR_API_LEVEL_3}
+ */
+ @Override
+ public DisplayMetrics getRearDisplayMetrics() {
+ DisplayMetrics metrics = null;
+
+ // DISPLAY_CATEGORY_REAR displays are only available when you are in the concurrent
+ // display state, so we have to look through all displays to match the address
+ Display[] displays = mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+ for (int i = 0; i < displays.length; i++) {
+ DisplayAddress.Physical address =
+ (DisplayAddress.Physical) displays[i].getAddress();
+ if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+ metrics = new DisplayMetrics();
+ displays[i].getRealMetrics(metrics);
+ break;
+ }
+ }
+
+ synchronized (mLock) {
+ // Update the rear display metrics with our latest value if one was received
+ if (metrics != null) {
+ mRearDisplayMetrics = metrics;
+ }
+
+ return Objects.requireNonNullElseGet(mRearDisplayMetrics, DisplayMetrics::new);
+ }
+ }
+
+ /**
* Adds a listener interested in receiving updates on the RearDisplayPresentationStatus
* of the device. Because this is being called from the OEM provided
* extensions, the result of the listener will be posted on the executor
@@ -260,8 +297,8 @@
return;
}
@WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
- DisplayMetrics metrics =
- currentStatus == STATUS_UNSUPPORTED ? null : getRearDisplayMetrics();
+ DisplayMetrics metrics = currentStatus == STATUS_UNSUPPORTED ? new DisplayMetrics()
+ : getRearDisplayMetrics();
consumer.accept(
new RearDisplayPresentationStatus(currentStatus, metrics));
}
@@ -491,37 +528,10 @@
}
}
- /**
- * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing
- * display was not found in the display list, but we have already computed the
- * {@link DisplayMetrics} for that display, we return the cached value.
- *
- * TODO(b/267563768): Update with guidance from Display team for missing displays.
- *
- * @throws IllegalArgumentException if the display is not found and there is no cached
- * {@link DisplayMetrics} for this display.
- */
- @GuardedBy("mLock")
- private DisplayMetrics getRearDisplayMetrics() {
- Display[] displays = mDisplayManager.getDisplays(
- DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
- for (int i = 0; i < displays.length; i++) {
- DisplayAddress.Physical address =
- (DisplayAddress.Physical) displays[i].getAddress();
- if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
- if (mRearDisplayMetrics == null) {
- mRearDisplayMetrics = new DisplayMetrics();
- }
- displays[i].getRealMetrics(mRearDisplayMetrics);
- return mRearDisplayMetrics;
- }
- }
- if (mRearDisplayMetrics != null) {
- return mRearDisplayMetrics;
- } else {
- throw new IllegalArgumentException(
- "No display found with the provided display address");
- }
+ private long getRearDisplayAddress(Context context) {
+ String address = context.getResources().getString(
+ R.string.config_rearDisplayPhysicalAddress);
+ return address.isEmpty() ? INVALID_DISPLAY_ADDRESS : Long.parseLong(address);
}
@GuardedBy("mLock")
diff --git a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
index 49491a7..69b339a 100644
--- a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
@@ -20,47 +20,48 @@
android:focusable="false"
android:focusableInTouchMode="false"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:theme="@style/ReachabilityEduHandLayout">
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_up_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_vertical|right"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_right_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_left_hand"
android:layout_gravity="center_vertical|left"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_left_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_down_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
</com.android.wm.shell.compatui.ReachabilityEduLayout>
diff --git a/libs/WindowManager/Shell/res/values-night/styles.xml b/libs/WindowManager/Shell/res/values-night/styles.xml
index 758c99d..4871f7a 100644
--- a/libs/WindowManager/Shell/res/values-night/styles.xml
+++ b/libs/WindowManager/Shell/res/values-night/styles.xml
@@ -17,12 +17,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="ReachabilityEduHandLayout">
+ <style name="ReachabilityEduHandLayout" parent="Theme.AppCompat">
<item name="android:focusable">false</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:background">@android:color/transparent</item>
- <item name="android:contentDescription">@string/restart_button_description</item>
- <item name="android:visibility">invisible</item>
<item name="android:lineSpacingExtra">-1sp</item>
<item name="android:textSize">12sp</item>
<item name="android:textAlignment">center</item>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 2b38888..8635c56 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -151,12 +151,10 @@
</item>
</style>
- <style name="ReachabilityEduHandLayout">
+ <style name="ReachabilityEduHandLayout" parent="Theme.AppCompat.Light">
<item name="android:focusable">false</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:background">@android:color/transparent</item>
- <item name="android:contentDescription">@string/restart_button_description</item>
- <item name="android:visibility">invisible</item>
<item name="android:lineSpacingExtra">-1sp</item>
<item name="android:textSize">12sp</item>
<item name="android:textAlignment">center</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3eb9fa2..6986810 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -17,12 +17,18 @@
package com.android.wm.shell.bubbles;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
+import static android.content.pm.ActivityInfo.CONFIG_DENSITY;
+import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
+import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
@@ -47,6 +53,7 @@
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -74,7 +81,6 @@
import android.util.SparseArray;
import android.view.IWindowManager;
import android.view.SurfaceControl;
-import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
@@ -102,6 +108,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
@@ -109,7 +116,6 @@
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -135,7 +141,7 @@
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationChangeListener,
+public class BubbleController implements ComponentCallbacks2,
RemoteCallable<BubbleController> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -153,7 +159,6 @@
private static final boolean BUBBLE_BAR_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
-
/**
* Common interface to send updates to bubble views.
*/
@@ -237,17 +242,17 @@
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
- private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
+ /**
+ * Saved configuration, used to detect changes in
+ * {@link #onConfigurationChanged(Configuration)}
+ */
+ private final Configuration mLastConfiguration = new Configuration();
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
-
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
- private float mFontScale = 0;
-
- /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
- private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+ /**
+ * Saved screen bounds, used to detect screen size changes in
+ * {@link #onConfigurationChanged(Configuration)}.
+ */
+ private final Rect mScreenBounds = new Rect();
/** Saved insets, used to detect WindowInset changes. */
private WindowInsets mWindowInsets;
@@ -293,7 +298,8 @@
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
- mContext = context;
+ mContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+ mLastConfiguration.setTo(mContext.getResources().getConfiguration());
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mLauncherApps = launcherApps;
@@ -317,11 +323,11 @@
mBubblePositioner = positioner;
mBubbleData = data;
mSavedUserBubbleData = new SparseArray<>();
- mBubbleIconFactory = new BubbleIconFactory(context,
- context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
- context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
- context.getResources().getDimensionPixelSize(
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
@@ -482,7 +488,6 @@
}
mCurrentProfiles = userProfiles;
- mShellController.addConfigurationChangeListener(this);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
@@ -774,6 +779,7 @@
try {
mAddedToWindowManager = true;
registerBroadcastReceiver();
+ mContext.registerComponentCallbacks(this);
mBubbleData.getOverflow().initialize(this);
// (TODO: b/273314541) some duplication in the inset listener
if (isShowingAsBubbleBar()) {
@@ -831,6 +837,7 @@
// Put on background for this binder call, was causing jank
mBackgroundExecutor.execute(() -> {
try {
+ mContext.unregisterComponentCallbacks(this);
mContext.unregisterReceiver(mBroadcastReceiver);
} catch (IllegalArgumentException e) {
// Not sure if this happens in production, but was happening in tests
@@ -930,8 +937,7 @@
mSavedUserBubbleData.remove(userId);
}
- @Override
- public void onThemeChanged() {
+ private void onThemeChanged() {
if (mStackView != null) {
mStackView.onThemeChanged();
}
@@ -963,34 +969,60 @@
}
}
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (mBubblePositioner != null) {
- mBubblePositioner.update();
- }
- if (mStackView != null && newConfig != null) {
- if (newConfig.densityDpi != mDensityDpi
- || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
- mDensityDpi = newConfig.densityDpi;
- mScreenBounds.set(newConfig.windowConfiguration.getBounds());
- mBubbleData.onMaxBubblesChanged();
- mBubbleIconFactory = new BubbleIconFactory(mContext,
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
- mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width));
- mStackView.onDisplaySizeChanged();
+ mMainExecutor.execute(() -> {
+ final int diff = newConfig.diff(mLastConfiguration);
+ final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
+ || (diff & CONFIG_UI_MODE) != 0;
+ if (themeChanged) {
+ onThemeChanged();
}
- if (newConfig.fontScale != mFontScale) {
- mFontScale = newConfig.fontScale;
- mStackView.updateFontScale();
+ if (mBubblePositioner != null) {
+ mBubblePositioner.update();
}
- if (newConfig.getLayoutDirection() != mLayoutDirection) {
- mLayoutDirection = newConfig.getLayoutDirection();
- mStackView.onLayoutDirectionChanged(mLayoutDirection);
+ if (mStackView != null) {
+ final boolean densityChanged = (diff & CONFIG_DENSITY) != 0;
+ final boolean fontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0;
+ final boolean layoutDirectionChanged = (diff & CONFIG_LAYOUT_DIRECTION) != 0;
+ if (densityChanged
+ || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
+ mScreenBounds.set(newConfig.windowConfiguration.getBounds());
+ mBubbleData.onMaxBubblesChanged();
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
+ mStackView.onDisplaySizeChanged();
+ }
+ if (fontScaleChanged) {
+ mStackView.updateFontScale();
+ }
+ if (layoutDirectionChanged) {
+ mStackView.onLayoutDirectionChanged(newConfig.getLayoutDirection());
+ }
}
- }
+ mLastConfiguration.setTo(newConfig);
+ });
+ }
+
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
+ @Override
+ public void onTrimMemory(int level) {
+ // Do nothing
+ }
+
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
+ @Override
+ public void onLowMemory() {
+ // Do nothing
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 28368ef..74ef57e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -77,6 +77,8 @@
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -562,6 +564,28 @@
}
//
+ // Keyguard transitions (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static KeyguardTransitionHandler provideKeyguardTransitionHandler(
+ ShellInit shellInit,
+ Transitions transitions,
+ @ShellMainThread Handler mainHandler,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new KeyguardTransitionHandler(
+ shellInit, transitions, mainHandler, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static KeyguardTransitions provideKeyguardTransitions(
+ KeyguardTransitionHandler handler) {
+ return handler.asKeyguardTransitions();
+ }
+
+ //
// Display areas
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f3130d3..be0288e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -60,6 +60,7 @@
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -532,9 +533,10 @@
Optional<SplitScreenController> splitScreenOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
Optional<RecentsTransitionHandler> recentsTransitionHandler,
+ KeyguardTransitionHandler keyguardTransitionHandler,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTouchHandlerOptional, recentsTransitionHandler);
+ pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
new file mode 100644
index 0000000..4d8075a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.keyguard;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
+
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.util.TransitionUtil.isClosingType;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
+
+import java.util.Map;
+
+/**
+ * The handler for Keyguard enter/exit and occlude/unocclude animations.
+ *
+ * <p>This takes the highest priority.
+ */
+public class KeyguardTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "KeyguardTransition";
+
+ private final Transitions mTransitions;
+ private final Handler mMainHandler;
+ private final ShellExecutor mMainExecutor;
+
+ private final Map<IBinder, IRemoteTransition> mStartedTransitions = new ArrayMap<>();
+
+ /**
+ * Local IRemoteTransition implementations registered by the keyguard service.
+ * @see KeyguardTransitions
+ */
+ private IRemoteTransition mExitTransition = null;
+ private IRemoteTransition mOccludeTransition = null;
+ private IRemoteTransition mOccludeByDreamTransition = null;
+ private IRemoteTransition mUnoccludeTransition = null;
+
+ public KeyguardTransitionHandler(
+ @NonNull ShellInit shellInit,
+ @NonNull Transitions transitions,
+ @NonNull Handler mainHandler,
+ @NonNull ShellExecutor mainExecutor) {
+ mTransitions = transitions;
+ mMainHandler = mainHandler;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mTransitions.addHandler(this);
+ }
+
+ /**
+ * Interface for SystemUI implementations to set custom Keyguard exit/occlude handlers.
+ */
+ @ExternalThread
+ public KeyguardTransitions asKeyguardTransitions() {
+ return new KeyguardTransitionsImpl();
+ }
+
+ public static boolean handles(TransitionInfo info) {
+ return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0
+ || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0
+ || info.getType() == TRANSIT_KEYGUARD_OCCLUDE
+ || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull TransitionFinishCallback finishCallback) {
+ if (!handles(info)) {
+ return false;
+ }
+
+ boolean hasOpeningOcclude = false;
+ boolean hasOpeningDream = false;
+ boolean hasClosingApp = false;
+
+ // Check for occluding/dream/closing apps
+ for (int i = info.getChanges().size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isOpeningType(change.getMode())) {
+ if (change.hasFlags(FLAG_OCCLUDES_KEYGUARD)) {
+ hasOpeningOcclude = true;
+ }
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM) {
+ hasOpeningDream = true;
+ }
+ } else if (isClosingType(change.getMode())) {
+ hasClosingApp = true;
+ }
+ }
+
+ // Choose a transition applicable for the changes and keyguard state.
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
+ return startAnimation(mExitTransition,
+ "going-away",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ }
+ if (hasOpeningOcclude || info.getType() == TRANSIT_KEYGUARD_OCCLUDE) {
+ if (hasOpeningDream) {
+ return startAnimation(mOccludeByDreamTransition,
+ "occlude-by-dream",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ } else {
+ return startAnimation(mOccludeTransition,
+ "occlude",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ }
+ } else if (hasClosingApp || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) {
+ return startAnimation(mUnoccludeTransition,
+ "unocclude",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ } else {
+ Log.wtf(TAG, "Failed to play: " + info);
+ return false;
+ }
+ }
+
+ private boolean startAnimation(IRemoteTransition remoteHandler, String description,
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "start keyguard %s transition, info = %s", description, info);
+
+ try {
+ remoteHandler.startAnimation(transition, info, startTransaction,
+ new IRemoteTransitionFinishedCallback.Stub() {
+ @Override
+ public void onTransitionFinished(
+ WindowContainerTransaction wct, SurfaceControl.Transaction sct) {
+ mMainExecutor.execute(() -> {
+ finishCallback.onTransitionFinished(wct, null);
+ });
+ }
+ });
+ mStartedTransitions.put(transition, remoteHandler);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "RemoteException thrown from local IRemoteTransition", e);
+ return false;
+ }
+ startTransaction.clear();
+ return true;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
+ @NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
+ @NonNull TransitionFinishCallback nextFinishCallback) {
+ final IRemoteTransition playing = mStartedTransitions.get(currentTransition);
+
+ if (playing == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "unknown keyguard transition %s", currentTransition);
+ return;
+ }
+
+ if (nextInfo.getType() == TRANSIT_SLEEP) {
+ // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
+ // token is held. In cases where keyguard is showing, we are running the animation for
+ // the device sleeping/waking, so it's best to ignore this and keep playing anyway.
+ return;
+ } else {
+ finishAnimationImmediately(currentTransition);
+ }
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted,
+ SurfaceControl.Transaction finishTransaction) {
+ finishAnimationImmediately(transition);
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ private void finishAnimationImmediately(IBinder transition) {
+ final IRemoteTransition playing = mStartedTransitions.get(transition);
+
+ if (playing != null) {
+ final IBinder fakeTransition = new Binder();
+ final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
+ final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
+ final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
+ try {
+ playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
+ } catch (RemoteException e) {
+ // There is no good reason for this to happen because the player is a local object
+ // implementing an AIDL interface.
+ Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
+ }
+ }
+ }
+
+ private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub {
+ @Override
+ public void onTransitionFinished(
+ WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+ return;
+ }
+ }
+
+ @ExternalThread
+ private final class KeyguardTransitionsImpl implements KeyguardTransitions {
+ @Override
+ public void register(
+ IRemoteTransition exitTransition,
+ IRemoteTransition occludeTransition,
+ IRemoteTransition occludeByDreamTransition,
+ IRemoteTransition unoccludeTransition) {
+ mMainExecutor.execute(() -> {
+ mExitTransition = exitTransition;
+ mOccludeTransition = occludeTransition;
+ mOccludeByDreamTransition = occludeByDreamTransition;
+ mUnoccludeTransition = unoccludeTransition;
+ });
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
new file mode 100644
index 0000000..b4b327f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.keyguard;
+
+import android.annotation.NonNull;
+import android.window.IRemoteTransition;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+/**
+ * Interface exposed to SystemUI Keyguard to register handlers for running
+ * animations on keyguard visibility changes.
+ *
+ * TODO(b/274954192): Merge the occludeTransition and occludeByDream handlers and just let the
+ * keyguard handler make the decision on which version it wants to play.
+ */
+@ExternalThread
+public interface KeyguardTransitions {
+ /**
+ * Registers a set of remote transitions for Keyguard.
+ */
+ default void register(
+ @NonNull IRemoteTransition unlockTransition,
+ @NonNull IRemoteTransition occludeTransition,
+ @NonNull IRemoteTransition occludeByDreamTransition,
+ @NonNull IRemoteTransition unoccludeTransition) {}
+}
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 6fa1861..42633b7 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
@@ -40,6 +40,7 @@
import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -63,6 +64,7 @@
private PipTransitionController mPipHandler;
private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
+ private final KeyguardTransitionHandler mKeyguardHandler;
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -76,6 +78,9 @@
/** Recents transition while split-screen foreground. */
static final int TYPE_RECENTS_DURING_SPLIT = 4;
+ /** Keyguard exit/occlude/unocclude transition. */
+ static final int TYPE_KEYGUARD = 5;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -126,8 +131,10 @@
public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
- Optional<RecentsTransitionHandler> recentsHandlerOptional) {
+ Optional<RecentsTransitionHandler> recentsHandlerOptional,
+ KeyguardTransitionHandler keyguardHandler) {
mPlayer = player;
+ mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
&& splitScreenControllerOptional.isPresent()) {
// Add after dependencies because it is higher priority
@@ -263,12 +270,26 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+
MixedTransition mixed = null;
for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
if (mActiveTransitions.get(i).mTransition != transition) continue;
mixed = mActiveTransitions.get(i);
break;
}
+
+ // Offer Keyguard the opportunity to take over lock transitions - ideally we could know by
+ // the time of handleRequest, but we need more information than is available at that time.
+ if (KeyguardTransitionHandler.handles(info)) {
+ if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Converting mixed transition into a keyguard transition");
+ onTransitionConsumed(transition, false, null);
+ }
+ mixed = new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+ mActiveTransitions.add(mixed);
+ }
+
if (mixed == null) return false;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
@@ -282,6 +303,9 @@
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
+ return mKeyguardHandler.startAnimation(
+ transition, info, startTransaction, finishTransaction, finishCallback);
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -574,6 +598,8 @@
}
mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
+ mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
@@ -597,6 +623,8 @@
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
+ mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index ef2a511..a242c72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -94,8 +94,7 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)
- && !TransitionUtil.alwaysReportToKeyguard(info)) {
+ if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)) {
// Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some
// operations of the start transaction may be ignored.
mRequestedRemotes.remove(transition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index ab27c55..f33b077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -71,6 +71,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -686,7 +687,11 @@
active.mToken, info, active.mStartT, active.mFinishT);
}
- if (info.getRootCount() == 0 && !TransitionUtil.alwaysReportToKeyguard(info)) {
+ /*
+ * Some transitions we always need to report to keyguard even if they are empty.
+ * TODO (b/274954192): Remove this once keyguard dispatching fully moves to Shell.
+ */
+ if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
// No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 402b0ce..ef0c7a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -85,23 +85,6 @@
return false;
}
- /**
- * Some transitions we always need to report to keyguard even if they are empty.
- * TODO (b/274954192): Remove this once keyguard dispatching moves to Shell.
- */
- public static boolean alwaysReportToKeyguard(TransitionInfo info) {
- // occlusion status of activities can change while screen is off so there will be no
- // visibility change but we still need keyguardservice to be notified.
- if (info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) return true;
-
- // It's possible for some activities to stop with bad timing (esp. since we can't yet
- // queue activity transitions initiated by apps) that results in an empty transition for
- // keyguard going-away. In general, we should should always report Keyguard-going-away.
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) return true;
-
- return false;
- }
-
/** Returns `true` if `change` is a wallpaper. */
public static boolean isWallpaper(TransitionInfo.Change change) {
return (change.getTaskInfo() == null)
diff --git a/packages/SettingsLib/res/drawable/ic_media_car.xml b/packages/SettingsLib/res/drawable/ic_media_car.xml
new file mode 100644
index 0000000..452e7bb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_car.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840L160,840Q143,840 131.5,828.5Q120,817 120,800L120,480L204,240Q210,222 225.5,211Q241,200 260,200L700,200Q719,200 734.5,211Q750,222 756,240L840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760ZM232,400L728,400L686,280L274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_computer.xml b/packages/SettingsLib/res/drawable/ic_media_computer.xml
new file mode 100644
index 0000000..2aa6f8e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_computer.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M80,840Q63,840 51.5,828.5Q40,817 40,800Q40,783 51.5,771.5Q63,760 80,760L880,760Q897,760 908.5,771.5Q920,783 920,800Q920,817 908.5,828.5Q897,840 880,840L80,840ZM160,720Q127,720 103.5,696.5Q80,673 80,640L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,640Q880,673 856.5,696.5Q833,720 800,720L160,720ZM160,640L800,640Q800,640 800,640Q800,640 800,640L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640ZM160,640Q160,640 160,640Q160,640 160,640L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640L160,640Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_game_console.xml b/packages/SettingsLib/res/drawable/ic_media_game_console.xml
new file mode 100644
index 0000000..8e422ac
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_game_console.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M189,800Q129,800 86.5,757Q44,714 42,653Q42,644 43,635Q44,626 46,617L130,281Q144,227 187,193.5Q230,160 285,160L675,160Q730,160 773,193.5Q816,227 830,281L914,617Q916,626 917.5,635.5Q919,645 919,654Q919,715 875.5,757.5Q832,800 771,800Q729,800 693,778Q657,756 639,718L611,660Q606,650 596,645Q586,640 575,640L385,640Q374,640 364,645Q354,650 349,660L321,718Q303,756 267,778Q231,800 189,800ZM192,720Q211,720 226.5,710Q242,700 250,683L278,626Q293,595 322,577.5Q351,560 385,560L575,560Q609,560 638,578Q667,596 683,626L711,683Q719,700 734.5,710Q750,720 769,720Q797,720 817,701.5Q837,683 838,655Q838,656 836,636L752,301Q745,274 724,257Q703,240 675,240L285,240Q257,240 235.5,257Q214,274 208,301L124,636Q122,642 122,654Q122,682 142.5,701Q163,720 192,720ZM540,440Q557,440 568.5,428.5Q580,417 580,400Q580,383 568.5,371.5Q557,360 540,360Q523,360 511.5,371.5Q500,383 500,400Q500,417 511.5,428.5Q523,440 540,440ZM620,360Q637,360 648.5,348.5Q660,337 660,320Q660,303 648.5,291.5Q637,280 620,280Q603,280 591.5,291.5Q580,303 580,320Q580,337 591.5,348.5Q603,360 620,360ZM620,520Q637,520 648.5,508.5Q660,497 660,480Q660,463 648.5,451.5Q637,440 620,440Q603,440 591.5,451.5Q580,463 580,480Q580,497 591.5,508.5Q603,520 620,520ZM700,440Q717,440 728.5,428.5Q740,417 740,400Q740,383 728.5,371.5Q717,360 700,360Q683,360 671.5,371.5Q660,383 660,400Q660,417 671.5,428.5Q683,440 700,440ZM340,500Q353,500 361.5,491.5Q370,483 370,470L370,430L410,430Q423,430 431.5,421.5Q440,413 440,400Q440,387 431.5,378.5Q423,370 410,370L370,370L370,330Q370,317 361.5,308.5Q353,300 340,300Q327,300 318.5,308.5Q310,317 310,330L310,370L270,370Q257,370 248.5,378.5Q240,387 240,400Q240,413 248.5,421.5Q257,430 270,430L310,430L310,470Q310,483 318.5,491.5Q327,500 340,500ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_smartwatch.xml b/packages/SettingsLib/res/drawable/ic_media_smartwatch.xml
new file mode 100644
index 0000000..9c73485
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_smartwatch.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_tablet.xml b/packages/SettingsLib/res/drawable/ic_media_tablet.xml
new file mode 100644
index 0000000..c773b96
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_tablet.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M200,920Q167,920 143.5,896.5Q120,873 120,840L120,120Q120,87 143.5,63.5Q167,40 200,40L760,40Q793,40 816.5,63.5Q840,87 840,120L840,840Q840,873 816.5,896.5Q793,920 760,920L200,920ZM200,720L200,840Q200,840 200,840Q200,840 200,840L760,840Q760,840 760,840Q760,840 760,840L760,720L200,720ZM400,800L560,800L560,760L400,760L400,800ZM200,640L760,640L760,240L200,240L200,640ZM200,160L760,160L760,120Q760,120 760,120Q760,120 760,120L200,120Q200,120 200,120Q200,120 200,120L200,160ZM200,160L200,120Q200,120 200,120Q200,120 200,120L200,120Q200,120 200,120Q200,120 200,120L200,160L200,160ZM200,720L200,720L200,840Q200,840 200,840Q200,840 200,840L200,840Q200,840 200,840Q200,840 200,840L200,720Z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index c036fdb..632120e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -15,10 +15,14 @@
*/
package com.android.settingslib.media;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK;
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_SMARTWATCH;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import android.content.Context;
@@ -31,8 +35,6 @@
import com.android.settingslib.R;
-import java.util.List;
-
/**
* InfoMediaDevice extends MediaDevice to represents wifi device.
*/
@@ -69,13 +71,12 @@
@Override
public Drawable getIconWithoutBackground() {
- return mContext.getDrawable(getDrawableResIdByFeature());
+ return mContext.getDrawable(getDrawableResIdByType());
}
@VisibleForTesting
- // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
- int getDrawableResId() {
+ int getDrawableResIdByType() {
int resId;
switch (mRouteInfo.getType()) {
case TYPE_GROUP:
@@ -84,6 +85,24 @@
case TYPE_REMOTE_TV:
resId = R.drawable.ic_media_display_device;
break;
+ case TYPE_REMOTE_TABLET:
+ resId = R.drawable.ic_media_tablet;
+ break;
+ case TYPE_REMOTE_TABLET_DOCKED:
+ resId = R.drawable.ic_dock_device;
+ break;
+ case TYPE_REMOTE_COMPUTER:
+ resId = R.drawable.ic_media_computer;
+ break;
+ case TYPE_REMOTE_GAME_CONSOLE:
+ resId = R.drawable.ic_media_game_console;
+ break;
+ case TYPE_REMOTE_CAR:
+ resId = R.drawable.ic_media_car;
+ break;
+ case TYPE_REMOTE_SMARTWATCH:
+ resId = R.drawable.ic_media_smartwatch;
+ break;
case TYPE_REMOTE_SPEAKER:
default:
resId = R.drawable.ic_media_speaker_device;
@@ -92,21 +111,6 @@
return resId;
}
- @VisibleForTesting
- int getDrawableResIdByFeature() {
- int resId;
- final List<String> features = mRouteInfo.getFeatures();
- if (features.contains(FEATURE_REMOTE_GROUP_PLAYBACK)) {
- resId = R.drawable.ic_media_group_device;
- } else if (features.contains(FEATURE_REMOTE_VIDEO_PLAYBACK)) {
- resId = R.drawable.ic_media_display_device;
- } else {
- resId = R.drawable.ic_media_speaker_device;
- }
-
- return resId;
- }
-
@Override
public String getId() {
return MediaDeviceUtils.getId(mRouteInfo);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 82c6f11..3fcb7f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -23,7 +23,13 @@
import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
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_SMARTWATCH;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
@@ -531,7 +537,6 @@
@SuppressWarnings("NewApi")
@VisibleForTesting
void addMediaDevice(MediaRoute2Info route) {
- //TODO(b/258141461): Attach flag and disable reason in MediaDevice
final int deviceType = route.getType();
MediaDevice mediaDevice = null;
switch (deviceType) {
@@ -539,7 +544,12 @@
case TYPE_REMOTE_TV:
case TYPE_REMOTE_SPEAKER:
case TYPE_GROUP:
- //TODO(b/148765806): use correct device type once api is ready.
+ case TYPE_REMOTE_TABLET:
+ case TYPE_REMOTE_TABLET_DOCKED:
+ case TYPE_REMOTE_COMPUTER:
+ case TYPE_REMOTE_GAME_CONSOLE:
+ case TYPE_REMOTE_CAR:
+ case TYPE_REMOTE_SMARTWATCH:
mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
mPackageName, mPreferenceItemMap.get(route.getId()));
break;
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 c45b7f3..67a045e 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
@@ -16,11 +16,14 @@
package com.android.settingslib.media;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK;
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_SMARTWATCH;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static com.google.common.truth.Truth.assertThat;
@@ -41,8 +44,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import java.util.ArrayList;
-
@RunWith(RobolectricTestRunner.class)
public class InfoMediaDeviceTest {
@@ -100,40 +101,47 @@
public void getDrawableResId_returnCorrectResId() {
when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_TV);
- assertThat(mInfoMediaDevice.getDrawableResId()).isEqualTo(
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
R.drawable.ic_media_display_device);
when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
- assertThat(mInfoMediaDevice.getDrawableResId()).isEqualTo(
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
R.drawable.ic_media_speaker_device);
when(mRouteInfo.getType()).thenReturn(TYPE_GROUP);
- assertThat(mInfoMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_group_device);
- }
-
- @Test
- public void getDrawableResIdByFeature_returnCorrectResId() {
- final ArrayList<String> features = new ArrayList<>();
- features.add(FEATURE_REMOTE_VIDEO_PLAYBACK);
- when(mRouteInfo.getFeatures()).thenReturn(features);
-
- assertThat(mInfoMediaDevice.getDrawableResIdByFeature()).isEqualTo(
- R.drawable.ic_media_display_device);
-
- features.clear();
- features.add(FEATURE_REMOTE_AUDIO_PLAYBACK);
- when(mRouteInfo.getFeatures()).thenReturn(features);
-
- assertThat(mInfoMediaDevice.getDrawableResIdByFeature()).isEqualTo(
- R.drawable.ic_media_speaker_device);
-
- features.clear();
- features.add(FEATURE_REMOTE_GROUP_PLAYBACK);
- when(mRouteInfo.getFeatures()).thenReturn(features);
-
- assertThat(mInfoMediaDevice.getDrawableResIdByFeature()).isEqualTo(
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
R.drawable.ic_media_group_device);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_TABLET);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_tablet);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_TABLET_DOCKED);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_dock_device);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_COMPUTER);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_computer);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_GAME_CONSOLE);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_game_console);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_CAR);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_car);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_SMARTWATCH);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_smartwatch);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9d3620e..ef4b814 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -116,6 +116,7 @@
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.AIRPLANE_MODE_ON,
Settings.Global.AIRPLANE_MODE_RADIOS,
+ Settings.Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS,
Settings.Global.SATELLITE_MODE_RADIOS,
Settings.Global.SATELLITE_MODE_ENABLED,
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index db8191b..a8febe7 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -112,9 +112,13 @@
android:focusable="true">
<TextView
+ android:id="@+id/magnifier_horizontal_lock_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:ellipsize="marquee"
android:text="@string/accessibility_allow_diagonal_scrolling"
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
android:layout_gravity="center_vertical" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5795e4f..7dc8afe 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2583,7 +2583,7 @@
<!-- Title for media controls [CHAR_LIMIT=50] -->
<string name="controls_media_title">Media</string>
<!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=50] -->
- <string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="YouTube Music">%1$s</xliff:g>?</string>
+ <string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g>?</string>
<!-- Explanation that controls associated with a specific media session are active [CHAR_LIMIT=50] -->
<string name="controls_media_active_session">The current media session cannot be hidden.</string>
<!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
@@ -2596,6 +2596,8 @@
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
<!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
<string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
+ <!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string>
<!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
<string name="controls_media_button_play">Play</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 510fcbf..a229b13 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -179,10 +179,10 @@
handleAttemptLockout(deadline);
}
}
+ mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
if (timeoutMs == 0) {
mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
}
- mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
startErrorAnimation();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index c1896fc..99bc32a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,7 +18,9 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.text.Editable;
import android.text.TextUtils;
+import android.text.TextWatcher;
import android.view.View;
import androidx.annotation.VisibleForTesting;
@@ -45,6 +47,31 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final AnnounceRunnable mAnnounceRunnable;
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable editable) {
+ CharSequence msg = editable;
+ if (!TextUtils.isEmpty(msg)) {
+ mView.removeCallbacks(mAnnounceRunnable);
+ mAnnounceRunnable.setTextToAnnounce(msg);
+ mView.postDelayed(() -> {
+ if (msg == mView.getText()) {
+ mAnnounceRunnable.run();
+ }
+ }, ANNOUNCEMENT_DELAY);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ /* no-op */
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ /* no-op */
+ }
+ };
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
@@ -89,12 +116,14 @@
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
mView.onThemeChanged();
+ mView.addTextChangedListener(mTextWatcher);
}
@Override
protected void onViewDetached() {
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ mView.removeTextChangedListener(mTextWatcher);
}
/**
@@ -113,12 +142,6 @@
*/
public void setMessage(CharSequence s, boolean animate) {
mView.setMessage(s, animate);
- CharSequence msg = mView.getText();
- if (!TextUtils.isEmpty(msg)) {
- mView.removeCallbacks(mAnnounceRunnable);
- mAnnounceRunnable.setTextToAnnounce(msg);
- mView.postDelayed(mAnnounceRunnable, ANNOUNCEMENT_DELAY);
- }
}
public void setMessage(int resId) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index b4ddc9a..2339747 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -82,9 +82,6 @@
protected void setPasswordEntryInputEnabled(boolean enabled) {
mPasswordEntry.setEnabled(enabled);
mOkButton.setEnabled(enabled);
- if (enabled && !mPasswordEntry.hasFocus()) {
- mPasswordEntry.requestFocus();
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index f23bb0a..1adaafb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -16,8 +16,6 @@
package com.android.keyguard;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.DEFAULT_PIN_LENGTH;
-
import android.view.View;
import com.android.internal.util.LatencyTracker;
@@ -42,10 +40,9 @@
private NumPadButton mBackspaceKey;
private View mOkButton = mView.findViewById(R.id.key_enter);
- private int mUserId;
private long mPinLength;
- private int mPasswordFailedAttempts;
+ private boolean mDisabledAutoConfirmation;
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -84,9 +81,8 @@
protected void onUserInput() {
super.onUserInput();
- if (isAutoConfirmation()) {
- updateOKButtonVisibility();
- updateBackSpaceVisibility();
+ if (isAutoPinConfirmEnabledInSettings()) {
+ updateAutoConfirmationState();
if (mPasswordEntry.getText().length() == mPinLength
&& mOkButton.getVisibility() == View.INVISIBLE) {
verifyPasswordAndUnlock();
@@ -103,13 +99,9 @@
@Override
public void startAppearAnimation() {
if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
- mUserId = KeyguardUpdateMonitor.getCurrentUser();
- mPinLength = mLockPatternUtils.getPinLength(mUserId);
- mBackspaceKey.setTransparentMode(/* isTransparentMode= */ isAutoConfirmation());
- updateOKButtonVisibility();
- updateBackSpaceVisibility();
+ mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
mPasswordEntry.setUsePinShapes(true);
- mPasswordEntry.setIsPinHinting(isAutoConfirmation() && isPinHinting());
+ updateAutoConfirmationState();
}
super.startAppearAnimation();
}
@@ -120,13 +112,25 @@
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
+ @Override
+ protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ super.handleAttemptLockout(elapsedRealtimeDeadline);
+ updateAutoConfirmationState();
+ }
+
+ private void updateAutoConfirmationState() {
+ mDisabledAutoConfirmation = mLockPatternUtils.getCurrentFailedPasswordAttempts(
+ KeyguardUpdateMonitor.getCurrentUser()) >= MIN_FAILED_PIN_ATTEMPTS;
+ updateOKButtonVisibility();
+ updateBackSpaceVisibility();
+ updatePinHinting();
+ }
/**
* Updates the visibility of the OK button for auto confirm feature
*/
private void updateOKButtonVisibility() {
- mPasswordFailedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(mUserId);
- if (isAutoConfirmation() && mPasswordFailedAttempts < MIN_FAILED_PIN_ATTEMPTS) {
+ if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) {
mOkButton.setVisibility(View.INVISIBLE);
} else {
mOkButton.setVisibility(View.VISIBLE);
@@ -134,33 +138,41 @@
}
/**
- * Updates the visibility and the enabled state of the backspace.
+ * Updates the visibility and the enabled state of the backspace.
* Visibility changes are only for auto confirmation configuration.
*/
private void updateBackSpaceVisibility() {
- if (!isAutoConfirmation()) {
- return;
+ boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings();
+ mBackspaceKey.setTransparentMode(/* isTransparentMode= */
+ isAutoConfirmation && !mDisabledAutoConfirmation);
+ if (isAutoConfirmation) {
+ if (mPasswordEntry.getText().length() > 0
+ || mDisabledAutoConfirmation) {
+ mBackspaceKey.setVisibility(View.VISIBLE);
+ } else {
+ mBackspaceKey.setVisibility(View.INVISIBLE);
+ }
}
-
- if (mPasswordEntry.getText().length() > 0) {
- mBackspaceKey.setVisibility(View.VISIBLE);
- } else {
- mBackspaceKey.setVisibility(View.INVISIBLE);
- }
+ }
+ /** Updates whether to use pin hinting or not. */
+ void updatePinHinting() {
+ mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting()
+ && !mDisabledAutoConfirmation);
}
/**
- * Responsible for identifying if PIN hinting is to be enabled or not
+ * Responsible for identifying if PIN hinting is to be enabled or not
*/
private boolean isPinHinting() {
- return mLockPatternUtils.getPinLength(mUserId) == DEFAULT_PIN_LENGTH;
+ return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
+ == DEFAULT_PIN_LENGTH;
}
/**
- * Responsible for identifying if auto confirm is enabled or not in Settings
+ * Responsible for identifying if auto confirm is enabled or not in Settings
*/
- private boolean isAutoConfirmation() {
+ private boolean isAutoPinConfirmEnabledInSettings() {
//Checks if user has enabled the auto confirm in Settings
- return mLockPatternUtils.isAutoPinConfirmEnabled(mUserId);
+ return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 95e97ff..f4cb5374 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -4395,7 +4395,8 @@
mFingerprintListenBuffer.toList()
).printTableData(pw);
}
-
+ pw.println("ActiveUnlockRunning="
+ + mTrustManager.isActiveUnlockRunning(KeyguardUpdateMonitor.getCurrentUser()));
new DumpsysTableLogger(
"KeyguardActiveUnlockTriggers",
KeyguardActiveUnlockModel.TABLE_HEADERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 6ae80a6..ebd234f 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -116,7 +116,12 @@
* @param isTransparentMode
*/
public void setTransparentMode(boolean isTransparentMode) {
+ if (mIsTransparentMode == isTransparentMode) {
+ return;
+ }
+
mIsTransparentMode = isTransparentMode;
+
if (isTransparentMode) {
setBackgroundColor(getResources().getColor(android.R.color.transparent));
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 5400011..8e8ee48 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -101,6 +101,7 @@
private Interpolator mFastOutSlowInInterpolator;
private boolean mShowPassword = true;
private UserActivityListener mUserActivityListener;
+ private boolean mIsPinHinting;
private PinShapeInput mPinShapeInput;
private boolean mUsePinShapes = false;
@@ -419,10 +420,15 @@
/**
* Determines whether AutoConfirmation feature is on.
*
- * @param usePinShapes
* @param isPinHinting
*/
public void setIsPinHinting(boolean isPinHinting) {
+ // Do not reinflate the view if we are using the same one.
+ if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
+ return;
+ }
+ mIsPinHinting = isPinHinting;
+
if (mPinShapeInput != null) {
removeView(mPinShapeInput.getView());
mPinShapeInput = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index f05152a..cb764a8 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -17,6 +17,7 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.ActiveUnlockModel
import com.android.systemui.keyguard.shared.model.TrustManagedModel
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.log.LogBuffer
@@ -76,6 +77,18 @@
)
}
+ fun activeUnlockModelEmitted(value: ActiveUnlockModel) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = value.userId
+ bool1 = value.isRunning
+ },
+ { "activeUnlockModel emitted: userId: $int1 isRunning: $bool1" }
+ )
+ }
+
fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
logBuffer.log(
TAG,
@@ -85,6 +98,15 @@
)
}
+ fun isCurrentUserActiveUnlockRunning(isCurrentUserActiveUnlockRunning: Boolean) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isCurrentUserActiveUnlockRunning },
+ { "isCurrentUserActiveUnlockRunning emitted: $bool1" }
+ )
+ }
+
fun isCurrentUserTrustManaged(isTrustManaged: Boolean) {
logBuffer.log(TAG, DEBUG, { bool1 = isTrustManaged }, { "isTrustManaged emitted: $bool1" })
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 632fcdc..2b468cf 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -27,6 +27,7 @@
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.util.InitializationChecker;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
@@ -93,6 +94,7 @@
.setBubbles(mWMComponent.getBubbles())
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
+ .setKeyguardTransitions(mWMComponent.getKeyguardTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
@@ -113,6 +115,7 @@
.setBubbles(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(new ShellTransitions() {})
+ .setKeyguardTransitions(new KeyguardTransitions() {})
.setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 44b49b8..3b1d695 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -53,6 +53,7 @@
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Switch;
+import android.widget.TextView;
import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
@@ -87,6 +88,7 @@
private SeekBarWithIconButtonsView mZoomSeekbar;
private LinearLayout mAllowDiagonalScrollingView;
+ private TextView mAllowDiagonalScrollingTitle;
private Switch mAllowDiagonalScrollingSwitch;
private LinearLayout mPanelView;
private LinearLayout mSettingView;
@@ -467,6 +469,8 @@
mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button);
mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
mFullScreenButton = mSettingView.findViewById(R.id.magnifier_full_button);
+ mAllowDiagonalScrollingTitle =
+ mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
float scale = mSecureSettings.getFloatForUser(
@@ -490,6 +494,7 @@
mDoneButton.setOnClickListener(mButtonClickListener);
mFullScreenButton.setOnClickListener(mButtonClickListener);
mEditButton.setOnClickListener(mButtonClickListener);
+ mAllowDiagonalScrollingTitle.setSelected(true);
mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
// Adds a pending post check to avoiding redundant calculation because this callback
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 1a0fcea..52355f3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -42,6 +42,7 @@
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -104,6 +105,9 @@
Builder setTransitions(ShellTransitions t);
@BindsInstance
+ Builder setKeyguardTransitions(KeyguardTransitions k);
+
+ @BindsInstance
Builder setStartingSurface(Optional<StartingSurface> s);
@BindsInstance
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 17d2332..b71871e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -31,6 +31,7 @@
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -99,6 +100,9 @@
ShellTransitions getTransitions();
@WMSingleton
+ KeyguardTransitions getKeyguardTransitions();
+
+ @WMSingleton
Optional<StartingSurface> getStartingSurface();
@WMSingleton
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 26fd086..18c4f60 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -235,7 +235,7 @@
/** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
// TODO(b/279794160): Tracking bug.
@JvmField
- val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+ val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 8b6bd24..6e77dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -21,7 +21,6 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
@@ -37,7 +36,6 @@
import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionOldType;
import static android.view.WindowManager.TransitionType;
-import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -67,8 +65,6 @@
import android.view.WindowManagerPolicyConstants;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionFilter;
import android.window.TransitionInfo;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -178,7 +174,7 @@
// Wrap Keyguard going away animation.
// Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
- private static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
+ public static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
return new IRemoteTransition.Stub() {
final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks =
new ArrayMap<>();
@@ -273,7 +269,8 @@
if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) {
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
final RemoteAnimationAdapter exitAnimationAdapter =
- new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
+ new RemoteAnimationAdapter(
+ mKeyguardViewMediator.getExitAnimationRunner(), 0, 0);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
exitAnimationAdapter);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
@@ -297,92 +294,7 @@
unoccludeAnimationAdapter);
ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
mDisplayTracker.getDefaultDisplayId(), definition);
- return;
}
- Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY");
- TransitionFilter f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
- mShellTransitions.registerRemote(f, new RemoteTransition(
- wrap(mExitAnimationRunner), getIApplicationThread(), "ExitKeyguard"));
-
- Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
- // Register for occluding
- final RemoteTransition occludeTransition = new RemoteTransition(
- mOccludeAnimation, getIApplicationThread(), "KeyguardOcclude");
- f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
- f.mRequirements = new TransitionFilter.Requirement[]{
- new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
- // First require at-least one app showing that occludes.
- f.mRequirements[0].mMustBeIndependent = false;
- f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- // Then require that we aren't closing any occludes (because this would mean a
- // regular task->task or activity->activity animation not involving keyguard).
- f.mRequirements[1].mNot = true;
- f.mRequirements[1].mMustBeIndependent = false;
- f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- mShellTransitions.registerRemote(f, occludeTransition);
-
- // Now register for un-occlude.
- final RemoteTransition unoccludeTransition = new RemoteTransition(
- mUnoccludeAnimation, getIApplicationThread(), "KeyguardUnocclude");
- f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
- f.mRequirements = new TransitionFilter.Requirement[]{
- new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
- // First require at-least one app going-away (doesn't need occlude flag
- // as that is implicit by it having been visible and we don't want to exclude
- // cases where we are un-occluding because the app removed its showWhenLocked
- // capability at runtime).
- f.mRequirements[1].mMustBeIndependent = false;
- f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- f.mRequirements[1].mMustBeTask = true;
- // Then require that we aren't opening any occludes (otherwise we'd remain
- // occluded).
- f.mRequirements[0].mNot = true;
- f.mRequirements[0].mMustBeIndependent = false;
- f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- mShellTransitions.registerRemote(f, unoccludeTransition);
-
- // Register for specific transition type.
- // Above filter cannot fulfill all conditions.
- // E.g. close top activity while screen off but next activity is occluded, this should
- // an occluded transition, but since the activity is invisible, the condition would
- // match unoccluded transition.
- // But on the contrary, if we add above condition in occluded transition, then when user
- // trying to dismiss occluded activity when unlock keyguard, the condition would match
- // occluded transition.
- f = new TransitionFilter();
- f.mTypeSet = new int[]{TRANSIT_KEYGUARD_OCCLUDE};
- mShellTransitions.registerRemote(f, occludeTransition);
-
- f = new TransitionFilter();
- f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE};
- mShellTransitions.registerRemote(f, unoccludeTransition);
-
- Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_OCCLUDE for DREAM");
- // Register for occluding by Dream
- f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
- f.mRequirements = new TransitionFilter.Requirement[]{
- new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
- // First require at-least one app of type DREAM showing that occludes.
- f.mRequirements[0].mActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM;
- f.mRequirements[0].mMustBeIndependent = false;
- f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- // Then require that we aren't closing any occludes (because this would mean a
- // regular task->task or activity->activity animation not involving keyguard).
- f.mRequirements[1].mNot = true;
- f.mRequirements[1].mMustBeIndependent = false;
- f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- mShellTransitions.registerRemote(f, new RemoteTransition(
- wrap(mKeyguardViewMediator.getOccludeByDreamAnimationRunner()),
- getIApplicationThread(), "KeyguardOccludeByDream"));
}
@Override
@@ -402,27 +314,6 @@
}
}
- private final IRemoteAnimationRunner.Stub mExitAnimationRunner =
- new IRemoteAnimationRunner.Stub() {
- @Override // Binder interface
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
- checkPermission();
- mKeyguardViewMediator.startKeyguardExitAnimation(transit, apps, wallpapers,
- nonApps, finishedCallback);
- Trace.endSection();
- }
-
- @Override // Binder interface
- public void onAnimationCancelled() {
- mKeyguardViewMediator.cancelKeyguardExitAnimation();
- }
- };
-
final IRemoteTransition mOccludeAnimation = new IRemoteTransition.Stub() {
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ddc12c9..2a94b08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -93,6 +93,7 @@
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import android.window.IRemoteTransition;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -153,12 +154,14 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Optional;
import java.util.concurrent.Executor;
/**
@@ -961,7 +964,26 @@
}
};
- private IRemoteAnimationRunner mOccludeAnimationRunner =
+ private final IRemoteAnimationRunner.Stub mExitAnimationRunner =
+ new IRemoteAnimationRunner.Stub() {
+ @Override // Binder interface
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
+ startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+ Trace.endSection();
+ }
+
+ @Override // Binder interface
+ public void onAnimationCancelled() {
+ cancelKeyguardExitAnimation();
+ }
+ };
+
+ private final IRemoteAnimationRunner mOccludeAnimationRunner =
new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController);
private final IRemoteAnimationRunner mOccludeByDreamAnimationRunner =
@@ -1003,7 +1025,8 @@
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo.topActivityType
+ final boolean isDream = (apps[0].taskInfo != null
+ && apps[0].taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
if (!isDream) {
Log.w(TAG, "The occluding app isn't Dream; "
@@ -1103,7 +1126,8 @@
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo.topActivityType
+ final boolean isDream = (apps[0].taskInfo != null
+ && apps[0].taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
final SyncRtSurfaceTransactionApplier applier =
@@ -1186,6 +1210,7 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
private ScreenOnCoordinator mScreenOnCoordinator;
+ private final KeyguardTransitions mKeyguardTransitions;
private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
private Lazy<ScrimController> mScrimControllerLazy;
@@ -1221,6 +1246,7 @@
ScreenOffAnimationController screenOffAnimationController,
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
+ KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeControllerLazy,
@@ -1248,6 +1274,7 @@
dumpManager.registerDumpable(getClass().getName(), this);
mDeviceConfig = deviceConfig;
mScreenOnCoordinator = screenOnCoordinator;
+ mKeyguardTransitions = keyguardTransitions;
mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -1323,6 +1350,12 @@
setShowingLocked(false /* showing */, true /* forceCallbacks */);
}
+ mKeyguardTransitions.register(
+ KeyguardService.wrap(getExitAnimationRunner()),
+ KeyguardService.wrap(getOccludeAnimationRunner()),
+ KeyguardService.wrap(getOccludeByDreamAnimationRunner()),
+ KeyguardService.wrap(getUnoccludeAnimationRunner()));
+
final ContentResolver cr = mContext.getContentResolver();
mDeviceInteractive = mPM.isInteractive();
@@ -1858,6 +1891,10 @@
Trace.endSection();
}
+ public IRemoteAnimationRunner getExitAnimationRunner() {
+ return mExitAnimationRunner;
+ }
+
public IRemoteAnimationRunner getOccludeAnimationRunner() {
return mOccludeAnimationRunner;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index deb8f5d..255556c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
import dagger.Module;
@@ -119,6 +120,7 @@
ScreenOffAnimationController screenOffAnimationController,
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
+ KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeController,
@@ -152,6 +154,7 @@
screenOffAnimationController,
notificationShadeDepthController,
screenOnCoordinator,
+ keyguardTransitions,
interactionJankMonitor,
dreamOverlayStateController,
shadeController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index f2f1c48..867675b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -22,6 +22,7 @@
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.keyguard.shared.model.ActiveUnlockModel
import com.android.systemui.keyguard.shared.model.TrustManagedModel
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.user.data.repository.UserRepository
@@ -29,7 +30,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -45,8 +45,8 @@
/** Flow representing whether the current user is trusted. */
val isCurrentUserTrusted: Flow<Boolean>
- /** Flow representing whether active unlock is available for the current user. */
- val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean>
+ /** Flow representing whether active unlock is running for the current user. */
+ val isCurrentUserActiveUnlockRunning: Flow<Boolean>
/** Reports that whether trust is managed has changed for the current user. */
val isCurrentUserTrustManaged: StateFlow<Boolean>
@@ -62,6 +62,7 @@
private val logger: TrustRepositoryLogger,
) : TrustRepository {
private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+ private val activeUnlockRunningForUser = mutableMapOf<Int, ActiveUnlockModel>()
private val trustManagedForUser = mutableMapOf<Int, TrustManagedModel>()
private val trust =
@@ -87,6 +88,17 @@
override fun onEnabledTrustAgentsChanged(userId: Int) = Unit
+ override fun onIsActiveUnlockRunningChanged(
+ isRunning: Boolean,
+ userId: Int
+ ) {
+ trySendWithFailureLogging(
+ ActiveUnlockModel(isRunning, userId),
+ TrustRepositoryLogger.TAG,
+ "onActiveUnlockRunningChanged"
+ )
+ }
+
override fun onTrustManagedChanged(isTrustManaged: Boolean, userId: Int) {
logger.onTrustManagedChanged(isTrustManaged, userId)
trySendWithFailureLogging(
@@ -95,11 +107,6 @@
"onTrustManagedChanged"
)
}
-
- override fun onIsActiveUnlockRunningChanged(
- isRunning: Boolean,
- userId: Int
- ) = Unit
}
trustManager.registerTrustListener(callback)
logger.trustListenerRegistered()
@@ -114,6 +121,10 @@
latestTrustModelForUser[it.userId] = it
logger.trustModelEmitted(it)
}
+ is ActiveUnlockModel -> {
+ activeUnlockRunningForUser[it.userId] = it
+ logger.activeUnlockModelEmitted(it)
+ }
is TrustManagedModel -> {
trustManagedForUser[it.userId] = it
logger.trustManagedModelEmitted(it)
@@ -122,8 +133,17 @@
}
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
- // TODO: Implement based on TrustManager callback b/267322286
- override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true)
+ override val isCurrentUserActiveUnlockRunning: Flow<Boolean> =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { activeUnlockRunningForUser[it.second.id]?.isRunning ?: false }
+ .distinctUntilChanged()
+ .onEach { logger.isCurrentUserActiveUnlockRunning(it) }
+ .onStart {
+ emit(
+ activeUnlockRunningForUser[userRepository.getSelectedUserInfo().id]?.isRunning
+ ?: false
+ )
+ }
override val isCurrentUserTrustManaged: StateFlow<Boolean>
get() =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 110bcd7..233146a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -31,6 +31,7 @@
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -43,11 +44,14 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.policy.KeyguardStateController
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import javax.inject.Inject
/**
@@ -58,18 +62,19 @@
class PrimaryBouncerInteractor
@Inject
constructor(
- private val repository: KeyguardBouncerRepository,
- private val primaryBouncerView: BouncerView,
- @Main private val mainHandler: Handler,
- private val keyguardStateController: KeyguardStateController,
- private val keyguardSecurityModel: KeyguardSecurityModel,
- private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
- private val falsingCollector: FalsingCollector,
- private val dismissCallbackRegistry: DismissCallbackRegistry,
- private val context: Context,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val trustRepository: TrustRepository,
- private val featureFlags: FeatureFlags,
+ private val repository: KeyguardBouncerRepository,
+ private val primaryBouncerView: BouncerView,
+ @Main private val mainHandler: Handler,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardSecurityModel: KeyguardSecurityModel,
+ private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+ private val falsingCollector: FalsingCollector,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
+ private val context: Context,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val trustRepository: TrustRepository,
+ private val featureFlags: FeatureFlags,
+ @Application private val applicationScope: CoroutineScope,
) {
private val passiveAuthBouncerDelay = context.resources.getInteger(
R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -104,6 +109,7 @@
/** Allow for interaction when just about fully visible */
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
+ private var currentUserActiveUnlockRunning = false
/** This callback needs to be a class field so it does not get garbage collected. */
val keyguardUpdateMonitorCallback =
@@ -122,6 +128,13 @@
init {
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ if (featureFlags.isEnabled(Flags.DELAY_BOUNCER)) {
+ applicationScope.launch {
+ trustRepository.isCurrentUserActiveUnlockRunning.collect {
+ currentUserActiveUnlockRunning = it
+ }
+ }
+ }
}
// TODO(b/243685699): Move isScrimmed logic to data layer.
@@ -377,8 +390,9 @@
private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
- val canRunActiveUnlock = trustRepository.isCurrentUserActiveUnlockAvailable.value &&
+ val canRunActiveUnlock = currentUserActiveUnlockRunning &&
keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
+
return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
!needsFullscreenBouncer() &&
(canRunFaceAuth || canRunActiveUnlock)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ActiveUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ActiveUnlockModel.kt
new file mode 100644
index 0000000..7309072
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ActiveUnlockModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.shared.model
+
+/** Represents the active unlock state */
+data class ActiveUnlockModel(
+ /** If true, the system believes active unlock is available and can be usd to unlock. */
+ val isRunning: Boolean,
+ /** The user, for which active unlock may be running. */
+ val userId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index fa42114..5079487 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -43,6 +43,7 @@
import android.net.Uri
import android.os.Parcelable
import android.os.Process
+import android.os.RemoteException
import android.os.UserHandle
import android.provider.Settings
import android.service.notification.StatusBarNotification
@@ -52,6 +53,7 @@
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IStatusBarService
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -137,6 +139,8 @@
expiryTimeMs = 0,
)
+const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
+
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
return sbn.notification.isMediaNotification()
}
@@ -181,6 +185,7 @@
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val statusBarService: IStatusBarService,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -252,6 +257,7 @@
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager,
+ statusBarService: IStatusBarService,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : this(
context,
@@ -277,6 +283,7 @@
logger,
smartspaceManager,
keyguardUpdateMonitor,
+ statusBarService,
)
private val appChangeReceiver =
@@ -378,21 +385,21 @@
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
if (useQsMediaPlayer && isMediaNotification(sbn)) {
- var logEvent = false
+ var isNewlyActiveEntry = false
Assert.isMainThread()
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
val instanceId = logger.getNewInstanceId()
val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
mediaEntries.put(key, temp)
- logEvent = true
+ isNewlyActiveEntry = true
} else if (oldKey != key) {
// Resume -> active conversion; move to new key
val oldData = mediaEntries.remove(oldKey)!!
- logEvent = true
+ isNewlyActiveEntry = true
mediaEntries.put(key, oldData)
}
- loadMediaData(key, sbn, oldKey, logEvent)
+ loadMediaData(key, sbn, oldKey, isNewlyActiveEntry)
} else {
onNotificationRemoved(key)
}
@@ -475,9 +482,9 @@
key: String,
sbn: StatusBarNotification,
oldKey: String?,
- logEvent: Boolean = false
+ isNewlyActiveEntry: Boolean = false,
) {
- backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) }
+ backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
}
/** Add a listener for changes in this class */
@@ -601,9 +608,11 @@
}
}
- private fun removeEntry(key: String) {
+ private fun removeEntry(key: String, logEvent: Boolean = true) {
mediaEntries.remove(key)?.let {
- logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
+ if (logEvent) {
+ logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
+ }
}
notifyMediaDataRemoved(key)
}
@@ -751,7 +760,7 @@
key: String,
sbn: StatusBarNotification,
oldKey: String?,
- logEvent: Boolean = false
+ isNewlyActiveEntry: Boolean = false,
) {
val token =
sbn.notification.extras.getParcelable(
@@ -772,6 +781,42 @@
)
?: getAppInfoFromPackage(sbn.packageName)
+ // App name
+ val appName = getAppName(sbn, appInfo)
+
+ // Song name
+ var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+ if (song == null) {
+ song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+ }
+ if (song == null) {
+ song = HybridGroupManager.resolveTitle(notif)
+ }
+ if (song.isNullOrBlank()) {
+ if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) {
+ // App is required to provide a title: cancel the underlying notification
+ try {
+ statusBarService.onNotificationError(
+ sbn.packageName,
+ sbn.tag,
+ sbn.id,
+ sbn.uid,
+ sbn.initialPid,
+ MEDIA_TITLE_ERROR_MESSAGE,
+ sbn.user.identifier
+ )
+ } catch (e: RemoteException) {
+ Log.e(TAG, "cancelNotification failed: $e")
+ }
+ // Only add log for media removed if active media is updated with invalid title.
+ foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) }
+ return
+ } else {
+ // For apps that don't have the title requirement yet, add a placeholder
+ song = context.getString(R.string.controls_media_empty_title, appName)
+ }
+ }
+
// Album art
var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
if (artworkBitmap == null) {
@@ -787,21 +832,9 @@
Icon.createWithBitmap(artworkBitmap)
}
- // App name
- val appName = getAppName(sbn, appInfo)
-
// App Icon
val smallIcon = sbn.notification.smallIcon
- // Song name
- var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
- if (song == null) {
- song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
- }
- if (song == null) {
- song = HybridGroupManager.resolveTitle(notif)
- }
-
// Explicit Indicator
var isExplicit = false
if (mediaFlags.isExplicitIndicatorEnabled()) {
@@ -873,7 +906,7 @@
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
val appUid = appInfo?.uid ?: Process.INVALID_UID
- if (logEvent) {
+ if (isNewlyActiveEntry) {
logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
} else if (playbackLocation != currentEntry?.playbackLocation) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 9bc66f6..3751c60 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -64,4 +64,9 @@
/** Check whether we allow remote media to generate resume controls */
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+
+ /** Check whether app is required to provide a non-empty media title */
+ fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean {
+ return StatusBarManager.isMediaTitleRequiredForApp(packageName, user)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index cdd00f9..a1e9995 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -29,6 +29,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.nearby.NearbyMediaDevicesManager
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import java.util.Optional
import javax.inject.Inject
@@ -49,7 +50,8 @@
private val audioManager: AudioManager,
private val powerExemptionManager: PowerExemptionManager,
private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags,
+ private val userTracker: UserTracker
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -61,7 +63,7 @@
val controller = MediaOutputController(context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags)
+ powerExemptionManager, keyGuardManager, featureFlags, userTracker)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 822644b..2713642 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -49,6 +49,7 @@
import android.media.NearbyDevice;
import android.media.RoutingSessionInfo;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.IBinder;
@@ -86,6 +87,7 @@
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -165,6 +167,7 @@
private float mInactiveRadius;
private float mActiveRadius;
private FeatureFlags mFeatureFlags;
+ private UserTracker mUserTracker;
public enum BroadcastNotifyDialog {
ACTION_FIRST_LAUNCH,
@@ -181,7 +184,8 @@
AudioManager audioManager,
PowerExemptionManager powerExemptionManager,
KeyguardManager keyGuardManager,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ UserTracker userTracker) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -192,6 +196,7 @@
mPowerExemptionManager = powerExemptionManager;
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
+ mUserTracker = userTracker;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -232,16 +237,13 @@
mNearbyMediaDevicesManager.registerNearbyDevicesCallback(this);
}
if (!TextUtils.isEmpty(mPackageName)) {
- for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
- if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
- mMediaController = controller;
- mMediaController.unregisterCallback(mCb);
- if (mMediaController.getPlaybackState() != null) {
- mCurrentState = mMediaController.getPlaybackState().getState();
- }
- mMediaController.registerCallback(mCb);
- break;
+ mMediaController = getMediaController();
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mCb);
+ if (mMediaController.getPlaybackState() != null) {
+ mCurrentState = mMediaController.getPlaybackState().getState();
}
+ mMediaController.registerCallback(mCb);
}
}
if (mMediaController == null) {
@@ -284,6 +286,26 @@
mNearbyDeviceInfoMap.clear();
}
+ private MediaController getMediaController() {
+ for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification.isMediaNotification()
+ && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+ MediaSession.Token token = notification.extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token.class);
+ return new MediaController(mContext, token);
+ }
+ }
+ for (MediaController controller : mMediaSessionManager.getActiveSessionsForUser(null,
+ mUserTracker.getUserHandle())) {
+ if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
+ return controller;
+ }
+ }
+ return null;
+ }
+
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
boolean isListEmpty =
@@ -1011,7 +1033,8 @@
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
- mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags);
+ mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
+ mUserTracker);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
dialog.show();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 7dbf876..8024886 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -32,6 +32,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.settings.UserTracker
import java.util.Optional
import javax.inject.Inject
@@ -51,7 +52,8 @@
private val audioManager: AudioManager,
private val powerExemptionManager: PowerExemptionManager,
private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags,
+ private val userTracker: UserTracker
) {
companion object {
private const val INTERACTION_JANK_TAG = "media_output"
@@ -67,7 +69,7 @@
context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags)
+ powerExemptionManager, keyGuardManager, featureFlags, userTracker)
val dialog =
MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index f7e7366..abeb5af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -153,14 +153,16 @@
@VisibleForTesting
/** Should be accessible only to the main thread. */
final Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap = new HashMap<>();
+ @VisibleForTesting
+ /** Should be accessible only to the main thread. */
+ final Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
+ @VisibleForTesting
+ /** Should be accessible only to the main thread. */
+ final Map<Integer, TelephonyCallback> mSubIdTelephonyCallbackMap = new HashMap<>();
private WifiManager mWifiManager;
private Context mContext;
private SubscriptionManager mSubscriptionManager;
- /** Should be accessible only to the main thread. */
- private Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
- /** Should be accessible only to the main thread. */
- private Map<Integer, TelephonyCallback> mSubIdTelephonyCallbackMap = new HashMap<>();
private TelephonyManager mTelephonyManager;
private ConnectivityManager mConnectivityManager;
private CarrierConfigTracker mCarrierConfigTracker;
@@ -320,6 +322,9 @@
Log.e(TAG, "Unexpected null telephony call back for Sub " + tm.getSubscriptionId());
}
}
+ mSubIdTelephonyManagerMap.clear();
+ mSubIdTelephonyCallbackMap.clear();
+ mSubIdTelephonyDisplayInfoMap.clear();
mSubscriptionManager.removeOnSubscriptionsChangedListener(
mOnSubscriptionsChangedListener);
mAccessPointController.removeAccessPointCallback(this);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index a35e5b5..d4522d0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -27,6 +27,8 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.text.Editable;
+import android.text.TextWatcher;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -93,16 +95,16 @@
}
@Test
- public void testSetMessage_AnnounceForAccessibility() {
- ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
- when(mKeyguardMessageArea.getText()).thenReturn("abc");
- mMessageAreaController.setMessage("abc");
+ public void textChanged_AnnounceForAccessibility() {
+ ArgumentCaptor<TextWatcher> textWatcherArgumentCaptor = ArgumentCaptor.forClass(
+ TextWatcher.class);
+ mMessageAreaController.onViewAttached();
+ verify(mKeyguardMessageArea).addTextChangedListener(textWatcherArgumentCaptor.capture());
- verify(mKeyguardMessageArea).setMessage("abc", /* animate= */ true);
+ textWatcherArgumentCaptor.getValue().afterTextChanged(
+ Editable.Factory.getInstance().newEditable("abc"));
verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class));
- verify(mKeyguardMessageArea).postDelayed(argumentCaptor.capture(), anyLong());
- argumentCaptor.getValue().run();
- verify(mKeyguardMessageArea).announceForAccessibility("abc");
+ verify(mKeyguardMessageArea).postDelayed(any(Runnable.class), anyLong());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 70476aa..d3b4190 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -152,9 +152,15 @@
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
- verify(deleteButton).visibility = View.INVISIBLE
+ verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
- verify(passwordTextView).setIsPinHinting(true)
+ verify(passwordTextView).setIsPinHinting(false)
+ }
+
+ @Test
+ fun handleLockout_readsNumberOfErrorAttempts() {
+ pinViewController.handleAttemptLockout(0)
+ verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 7531cb4..ffad326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -24,13 +24,14 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
@@ -95,8 +96,9 @@
mock(DismissCallbackRegistry::class.java),
context,
mKeyguardUpdateMonitor,
- mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeTrustRepository(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ testScope.backgroundScope,
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
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 489dc4d..f31ac00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -95,6 +95,7 @@
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import org.junit.Before;
import org.junit.Test;
@@ -135,6 +136,7 @@
private @Mock ScreenOffAnimationController mScreenOffAnimationController;
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
+ private @Mock KeyguardTransitions mKeyguardTransitions;
private @Mock ShadeController mShadeController;
private NotificationShadeWindowController mNotificationShadeWindowController;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
@@ -620,6 +622,7 @@
mScreenOffAnimationController,
() -> mNotificationShadeDepthController,
mScreenOnCoordinator,
+ mKeyguardTransitions,
mInteractionJankMonitor,
mDreamOverlayStateController,
() -> mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index 8611359..29d7500 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -64,7 +64,6 @@
testScope = TestScope()
userRepository = FakeUserRepository()
userRepository.setUserInfos(users)
-
val logger =
TrustRepositoryLogger(
LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
@@ -224,4 +223,41 @@
assertThat(isCurrentUserTrusted()).isTrue()
}
+
+ @Test
+ fun isCurrentUserActiveUnlockRunning_runningFirstBeforeUserInfoChanges_emitsCorrectValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listener.capture())
+ val isCurrentUserActiveUnlockRunning by
+ collectLastValue(underTest.isCurrentUserActiveUnlockRunning)
+ userRepository.setSelectedUserInfo(users[1])
+
+ // active unlock running = true for users[0].id, but not the current user
+ listener.value.onIsActiveUnlockRunningChanged(true, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isFalse()
+
+ // current user is now users[0].id
+ userRepository.setSelectedUserInfo(users[0])
+ assertThat(isCurrentUserActiveUnlockRunning).isTrue()
+ }
+
+ @Test
+ fun isCurrentUserActiveUnlockRunning_whenActiveUnlockRunningForCurrentUser_emitsNewValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listener.capture())
+ val isCurrentUserActiveUnlockRunning by
+ collectLastValue(underTest.isCurrentUserActiveUnlockRunning)
+ userRepository.setSelectedUserInfo(users[0])
+
+ listener.value.onIsActiveUnlockRunningChanged(true, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isTrue()
+
+ listener.value.onIsActiveUnlockRunningChanged(false, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isFalse()
+
+ listener.value.onIsActiveUnlockRunningChanged(true, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index e261982..6af1220 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -35,7 +35,7 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -100,8 +100,9 @@
mock(DismissCallbackRegistry::class.java),
context,
keyguardUpdateMonitor,
- mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeTrustRepository(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ testScope.backgroundScope,
),
AlternateBouncerInteractor(
mock(StatusBarStateController::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index eed1e739..5d39794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -75,6 +77,7 @@
private lateinit var resources: TestableResources
private lateinit var trustRepository: FakeTrustRepository
private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
@@ -83,9 +86,10 @@
.thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
DejankUtils.setImmediate(true)
+ testScope = TestScope()
mainHandler = FakeHandler(android.os.Looper.getMainLooper())
trustRepository = FakeTrustRepository()
- featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, false) }
+ featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }
underTest =
PrimaryBouncerInteractor(
repository,
@@ -100,6 +104,7 @@
keyguardUpdateMonitor,
trustRepository,
featureFlags,
+ testScope.backgroundScope,
)
whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -398,7 +403,6 @@
mainHandler.setMode(FakeHandler.Mode.QUEUEING)
// GIVEN bouncer should be delayed due to face auth
- featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
.thenReturn(true)
@@ -420,26 +424,29 @@
@Test
fun delayBouncerWhenActiveUnlockPossible() {
- mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+ testScope.run {
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
- // GIVEN bouncer should be delayed due to active unlock
- featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
- trustRepository.setCurrentUserActiveUnlockAvailable(true)
- whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()).thenReturn(true)
+ // GIVEN bouncer should be delayed due to active unlock
+ trustRepository.setCurrentUserActiveUnlockAvailable(true)
+ whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
+ .thenReturn(true)
+ runCurrent()
- // WHEN bouncer show is requested
- underTest.show(true)
+ // WHEN bouncer show is requested
+ underTest.show(true)
- // THEN primary show & primary showing soon were scheduled to update
- verify(repository, never()).setPrimaryShow(true)
- verify(repository, never()).setPrimaryShowingSoon(false)
+ // THEN primary show & primary showing soon were scheduled to update
+ verify(repository, never()).setPrimaryShow(true)
+ verify(repository, never()).setPrimaryShowingSoon(false)
- // WHEN all queued messages are dispatched
- mainHandler.dispatchQueuedMessages()
+ // WHEN all queued messages are dispatched
+ mainHandler.dispatchQueuedMessages()
- // THEN primary show & primary showing soon are updated
- verify(repository).setPrimaryShow(true)
- verify(repository).setPrimaryShowingSoon(false)
+ // THEN primary show & primary showing soon are updated
+ verify(repository).setPrimaryShow(true)
+ verify(repository).setPrimaryShowingSoon(false)
+ }
}
private fun updateSideFpsVisibilityParameters(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index 5056b43..b288fbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
@@ -34,6 +35,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -75,7 +77,8 @@
context,
keyguardUpdateMonitor,
Mockito.mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ TestScope().backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index e9f1ac1..15707c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
@@ -38,6 +39,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -81,7 +83,8 @@
context,
keyguardUpdateMonitor,
Mockito.mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ TestScope().backgroundScope,
)
underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index d428db7b..fd6e457 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -25,6 +25,7 @@
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceTarget
import android.content.Intent
+import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.media.MediaDescription
@@ -40,6 +41,7 @@
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IStatusBarService
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.R
@@ -76,6 +78,7 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -130,6 +133,7 @@
@Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var statusBarService: IStatusBarService
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -192,7 +196,8 @@
mediaFlags = mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
- keyguardUpdateMonitor = keyguardUpdateMonitor
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ statusBarService = statusBarService,
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -517,19 +522,211 @@
}
@Test
- fun testOnNotificationRemoved_emptyTitle_notConverted() {
- // GIVEN that the manager has a notification with a resume action and empty title.
+ fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() {
+ // When the manager has a notification with an empty title, and the app is required
+ // to include a non-empty title
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
whenever(controller.metadata)
.thenReturn(
metadataBuilder
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
.build()
)
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then the media control is not added and we report a notification error
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(statusBarService)
+ .onNotificationError(
+ eq(PACKAGE_NAME),
+ eq(mediaNotification.tag),
+ eq(mediaNotification.id),
+ eq(mediaNotification.uid),
+ eq(mediaNotification.initialPid),
+ eq(MEDIA_TITLE_ERROR_MESSAGE),
+ eq(mediaNotification.user.identifier)
+ )
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
+ verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
+ }
+
+ @Test
+ fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() {
+ // When the manager has a notification with a blank title, and the app is required
+ // to include a non-empty title
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then the media control is not added and we report a notification error
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(statusBarService)
+ .onNotificationError(
+ eq(PACKAGE_NAME),
+ eq(mediaNotification.tag),
+ eq(mediaNotification.id),
+ eq(mediaNotification.uid),
+ eq(mediaNotification.initialPid),
+ eq(MEDIA_TITLE_ERROR_MESSAGE),
+ eq(mediaNotification.user.identifier)
+ )
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
+ verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
+ }
+
+ @Test
+ fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() {
+ // When the app is required to provide a non-blank title, and updates a previously valid
+ // title to an empty one
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+
+ reset(listener)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then the media control is removed
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(statusBarService)
+ .onNotificationError(
+ eq(PACKAGE_NAME),
+ eq(mediaNotification.tag),
+ eq(mediaNotification.id),
+ eq(mediaNotification.uid),
+ eq(mediaNotification.initialPid),
+ eq(MEDIA_TITLE_ERROR_MESSAGE),
+ eq(mediaNotification.user.identifier)
+ )
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ }
+
+ @Test
+ fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() {
+ // When the manager has a notification with an empty title, and the app is not
+ // required to include a non-empty title
+ val mockPackageManager = mock(PackageManager::class.java)
+ context.setMockPackageManager(mockPackageManager)
+ whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then a media control is created with a placeholder title string
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+ assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
+ }
+
+ @Test
+ fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() {
+ // GIVEN that the manager has a notification with a blank title, and the app is not
+ // required to include a non-empty title
+ val mockPackageManager = mock(PackageManager::class.java)
+ context.setMockPackageManager(mockPackageManager)
+ whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then a media control is created with a placeholder title string
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+ assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
+ }
+
+ @Test
+ fun testOnNotificationRemoved_emptyTitle_notConverted() {
+ // GIVEN that the manager has a notification with a resume action and empty title.
addNotificationAndLoad()
val data = mediaDataCaptor.value
val instanceId = data.instanceId
assertThat(data.resumption).isFalse()
- mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+ mediaDataManager.onMediaDataLoaded(
+ KEY,
+ null,
+ data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {})
+ )
// WHEN the notification is removed
reset(listener)
@@ -554,17 +751,15 @@
@Test
fun testOnNotificationRemoved_blankTitle_notConverted() {
// GIVEN that the manager has a notification with a resume action and blank title.
- whenever(controller.metadata)
- .thenReturn(
- metadataBuilder
- .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
- .build()
- )
addNotificationAndLoad()
val data = mediaDataCaptor.value
val instanceId = data.instanceId
assertThat(data.resumption).isFalse()
- mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+ mediaDataManager.onMediaDataLoaded(
+ KEY,
+ null,
+ data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {})
+ )
// WHEN the notification is removed
reset(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 480d59c..f79c53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -54,6 +54,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.Before;
@@ -91,6 +92,7 @@
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
+ private UserTracker mUserTracker = mock(UserTracker.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -123,7 +125,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 891a6f8..705b485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.After;
@@ -95,6 +96,7 @@
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
+ private UserTracker mUserTracker = mock(UserTracker.class);
private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
private MediaOutputController mMediaOutputController;
@@ -109,7 +111,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
mBroadcastSender, mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 299303d..8f7bad6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
@@ -48,13 +49,18 @@
import android.media.MediaRoute2Info;
import android.media.NearbyDevice;
import android.media.RoutingSessionInfo;
+import android.media.session.ISessionController;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.text.TextUtils;
import android.view.View;
@@ -73,6 +79,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -91,6 +98,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaOutputControllerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "com.test.package.name";
@@ -111,7 +119,7 @@
private NearbyMediaDevicesManager mNearbyMediaDevicesManager;
// Mock
@Mock
- private MediaController mMediaController;
+ private MediaController mSessionMediaController;
@Mock
private MediaSessionManager mMediaSessionManager;
@Mock
@@ -151,10 +159,15 @@
@Mock
private PlaybackState mPlaybackState;
+ @Mock
+ private UserTracker mUserTracker;
+
private FeatureFlags mFlags = mock(FeatureFlags.class);
private View mDialogLaunchView = mock(View.class);
private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
+ final Notification mNotification = mock(Notification.class);
+
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
private LocalMediaManager mLocalMediaManager;
@@ -169,10 +182,14 @@
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPackageManager);
mSpyContext = spy(mContext);
- when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
- mMediaControllers.add(mMediaController);
- when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
+ final UserHandle userHandle = mock(UserHandle.class);
+ when(mUserTracker.getUserHandle()).thenReturn(userHandle);
+ when(mSessionMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(mSessionMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+ mMediaControllers.add(mSessionMediaController);
+ when(mMediaSessionManager.getActiveSessionsForUser(any(),
+ Mockito.eq(userHandle))).thenReturn(
+ mMediaControllers);
doReturn(mMediaSessionManager).when(mSpyContext).getSystemService(
MediaSessionManager.class);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
@@ -182,7 +199,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(false);
when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING)).thenReturn(false);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
@@ -205,6 +222,24 @@
when(mNearbyDevice2.getRangeZone()).thenReturn(NearbyDevice.RANGE_CLOSE);
mNearbyDevices.add(mNearbyDevice1);
mNearbyDevices.add(mNearbyDevice2);
+
+ final List<NotificationEntry> entryList = new ArrayList<>();
+ final NotificationEntry entry = mock(NotificationEntry.class);
+ final StatusBarNotification sbn = mock(StatusBarNotification.class);
+ final Bundle bundle = mock(Bundle.class);
+ final MediaSession.Token token = mock(MediaSession.Token.class);
+ final ISessionController binder = mock(ISessionController.class);
+ entryList.add(entry);
+
+ when(mNotification.isMediaNotification()).thenReturn(false);
+ when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
+ when(entry.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(mNotification);
+ when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ mNotification.extras = bundle;
+ when(bundle.getParcelable(Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token.class)).thenReturn(token);
+ when(token.getBinder()).thenReturn(binder);
}
@Test
@@ -227,10 +262,19 @@
}
@Test
- public void start_withPackageName_verifyMediaControllerInit() {
+ public void start_notificationNotFound_mediaControllerInitFromSession() {
mMediaOutputController.start(mCb);
- verify(mMediaController).registerCallback(any());
+ verify(mSessionMediaController).registerCallback(any());
+ }
+
+ @Test
+ public void start_MediaNotificationFound_mediaControllerNotInitFromSession() {
+ when(mNotification.isMediaNotification()).thenReturn(true);
+ mMediaOutputController.start(mCb);
+
+ verify(mSessionMediaController, never()).registerCallback(any());
+ verifyZeroInteractions(mMediaSessionManager);
}
@Test
@@ -239,11 +283,11 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.start(mCb);
- verify(mMediaController, never()).registerCallback(any());
+ verify(mSessionMediaController, never()).registerCallback(any());
}
@Test
@@ -256,11 +300,11 @@
@Test
public void stop_withPackageName_verifyMediaControllerDeinit() {
mMediaOutputController.start(mCb);
- reset(mMediaController);
+ reset(mSessionMediaController);
mMediaOutputController.stop();
- verify(mMediaController).unregisterCallback(any());
+ verify(mSessionMediaController).unregisterCallback(any());
}
@Test
@@ -269,19 +313,19 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.start(mCb);
mMediaOutputController.stop();
- verify(mMediaController, never()).unregisterCallback(any());
+ verify(mSessionMediaController, never()).unregisterCallback(any());
}
@Test
public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() {
mMediaOutputController.start(mCb);
- reset(mMediaController);
+ reset(mSessionMediaController);
mMediaOutputController.stop();
@@ -509,7 +553,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -532,7 +576,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -568,7 +612,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -585,7 +629,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -684,7 +728,7 @@
@Test
public void isPlaying_stateIsNull() {
- when(mMediaController.getPlaybackState()).thenReturn(null);
+ when(mSessionMediaController.getPlaybackState()).thenReturn(null);
assertThat(mMediaOutputController.isPlaying()).isFalse();
}
@@ -726,7 +770,7 @@
@Test
public void getHeaderTitle_withoutMetadata_returnDefaultString() {
- when(mMediaController.getMetadata()).thenReturn(null);
+ when(mSessionMediaController.getMetadata()).thenReturn(null);
mMediaOutputController.start(mCb);
@@ -736,7 +780,7 @@
@Test
public void getHeaderTitle_withMetadata_returnSongName() {
- when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
+ when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
mMediaOutputController.start(mCb);
@@ -745,7 +789,7 @@
@Test
public void getHeaderSubTitle_withoutMetadata_returnNull() {
- when(mMediaController.getMetadata()).thenReturn(null);
+ when(mSessionMediaController.getMetadata()).thenReturn(null);
mMediaOutputController.start(mCb);
@@ -754,7 +798,7 @@
@Test
public void getHeaderSubTitle_withMetadata_returnArtistName() {
- when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
+ when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
mMediaOutputController.start(mCb);
@@ -868,7 +912,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -1060,7 +1104,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 425d0bc..f3aee48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -34,6 +34,7 @@
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.PowerExemptionManager;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.FeatureFlagUtils;
@@ -55,12 +56,14 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -98,6 +101,7 @@
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
+ private UserTracker mUserTracker = mock(UserTracker.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
@@ -119,13 +123,17 @@
when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
when(mMediaMetadata.getDescription()).thenReturn(mMediaDescription);
mMediaControllers.add(mMediaController);
- when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
+ final UserHandle userHandle = mock(UserHandle.class);
+ when(mUserTracker.getUserHandle()).thenReturn(userHandle);
+ when(mMediaSessionManager.getActiveSessionsForUser(any(),
+ Mockito.eq(userHandle))).thenReturn(
+ mMediaControllers);
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = makeTestDialog(mMediaOutputController);
mMediaOutputDialog.show();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 84cc977..6614392 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -43,6 +43,7 @@
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
@@ -961,6 +962,26 @@
assertThat(dds).contains(mContext.getString(R.string.carrier_network_change_mode));
}
+ @Test
+ public void onStop_cleanUp() {
+ doReturn(SUB_ID).when(mTelephonyManager).getSubscriptionId();
+ assertThat(mInternetDialogController.mSubIdTelephonyManagerMap.get(SUB_ID)).isEqualTo(
+ mTelephonyManager);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.get(SUB_ID)).isNotNull();
+
+ mInternetDialogController.onStop();
+
+ verify(mTelephonyManager).unregisterTelephonyCallback(any(TelephonyCallback.class));
+ assertThat(mInternetDialogController.mSubIdTelephonyDisplayInfoMap.isEmpty()).isTrue();
+ assertThat(mInternetDialogController.mSubIdTelephonyManagerMap.isEmpty()).isTrue();
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.isEmpty()).isTrue();
+ verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(mInternetDialogController
+ .mOnSubscriptionsChangedListener);
+ verify(mAccessPointController).removeAccessPointCallback(mInternetDialogController);
+ verify(mConnectivityManager).unregisterNetworkCallback(
+ any(ConnectivityManager.NetworkCallback.class));
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 47a86b1..f0683a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -300,6 +300,10 @@
private UserHandle mUser0;
+ // The window context being used by the controller, use this to verify
+ // any actions on the context.
+ private Context mBubbleControllerContext;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -436,6 +440,8 @@
// Get a reference to KeyguardStateController.Callback
verify(mKeyguardStateController, atLeastOnce())
.addCallback(mKeyguardStateControllerCallbackCaptor.capture());
+
+ mBubbleControllerContext = mBubbleController.getContext();
}
@After
@@ -468,11 +474,6 @@
}
@Test
- public void instantiateController_registerConfigChangeListener() {
- verify(mShellController, times(1)).addConfigurationChangeListener(any());
- }
-
- @Test
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1385,13 +1386,28 @@
assertStackCollapsed();
}
+ @Test
+ public void testRegisterUnregisterComponentCallbacks() {
+ spyOn(mBubbleControllerContext);
+ mBubbleController.updateBubble(mBubbleEntry);
+ verify(mBubbleControllerContext).registerComponentCallbacks(eq(mBubbleController));
+
+ mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
+ // TODO: not certain why this isn't called normally when tests are run, perhaps because
+ // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
+ mBubbleController.onAllBubblesAnimatedOut();
+
+ verify(mBubbleControllerContext).unregisterComponentCallbacks(eq(mBubbleController));
+ }
@Test
public void testRegisterUnregisterBroadcastListener() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
assertThat(mFilterArgumentCaptor.getValue()
.hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)).isTrue();
assertThat(mFilterArgumentCaptor.getValue()
@@ -1402,47 +1418,54 @@
// it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
mBubbleController.onAllBubblesAnimatedOut();
- verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
+ verify(mBubbleControllerContext).unregisterReceiver(
+ eq(mBroadcastReceiverArgumentCaptor.getValue()));
}
@Test
public void testBroadcastReceiverCloseDialogs_notGestureNav() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackExpanded();
}
@Test
public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
i.putExtra("reason", "gestureNav");
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackCollapsed();
}
@Test
public void testBroadcastReceiver_screenOff() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackCollapsed();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 5ff57aa..4b6dd3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManager;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.testing.LeakCheck;
@@ -62,6 +63,10 @@
return (SysuiTestableContext) createDisplayContext(display);
}
+ public SysuiTestableContext createWindowContext(int type, Bundle bundle) {
+ return new SysuiTestableContext(getBaseContext().createWindowContext(type, bundle));
+ }
+
public void cleanUpReceivers(String testName) {
Set<BroadcastReceiver> copy;
synchronized (mRegisteredReceivers) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index 1340a47..817e1db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -28,7 +28,7 @@
get() = _isCurrentUserTrusted
private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false)
- override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> =
+ override val isCurrentUserActiveUnlockRunning: StateFlow<Boolean> =
_isCurrentUserActiveUnlockAvailable.asStateFlow()
private val _isCurrentUserTrustManaged = MutableStateFlow(false)
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
index 93531dd..92fd419 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.magnification;
+import android.annotation.NonNull;
+import android.content.Context;
import android.provider.DeviceConfig;
/**
@@ -29,6 +31,13 @@
private static final String FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION =
"AlwaysOnMagnifier__enable_always_on_magnifier";
+ private @NonNull Context mContext;
+
+ AlwaysOnMagnificationFeatureFlag(@NonNull Context context) {
+ super();
+ mContext = context;
+ }
+
@Override
String getNamespace() {
return NAMESPACE;
@@ -41,6 +50,7 @@
@Override
boolean getDefaultValue() {
- return false;
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_magnification_always_on_enabled);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 9b1c204..87fbee7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -156,7 +156,7 @@
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
FEATURE_WINDOW_MAGNIFICATION);
- mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag();
+ mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
mBackgroundExecutor, mAms::updateAlwaysOnMagnification);
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index af5b196..fc758cb 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -761,6 +761,12 @@
}
// Called by Shell command
+ String getFieldDetectionServiceName(@UserIdInt int userId) {
+ enforceCallingPermissionForManagement();
+ return mFieldClassificationResolver.readServiceName(userId);
+ }
+
+ // Called by Shell command
boolean setTemporaryDetectionService(@UserIdInt int userId, @NonNull String serviceName,
int durationMs) {
Slog.i(mTag, "setTemporaryDetectionService(" + userId + ") to " + serviceName
@@ -903,9 +909,9 @@
}
/**
- * Whether the Autofill PCC Classification feature is enabled.
+ * Whether the Autofill PCC Classification feature flag is enabled.
*/
- public boolean isPccClassificationEnabled() {
+ public boolean isPccClassificationFlagEnabled() {
synchronized (mFlagLock) {
return mPccClassificationEnabled;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d5dcdaf..63a607c 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -1730,6 +1730,23 @@
return mRemoteFieldClassificationService;
}
+
+ public boolean isPccClassificationEnabled() {
+ boolean result = isPccClassificationEnabledInternal();
+ if (sVerbose) {
+ Slog.v(TAG, "pccEnabled: " + result);
+ }
+ return result;
+ }
+
+ public boolean isPccClassificationEnabledInternal() {
+ boolean flagEnabled = mMaster.isPccClassificationFlagEnabled();
+ if (!flagEnabled) return false;
+ synchronized (mLock) {
+ return getRemoteFieldClassificationServiceLocked() != null;
+ }
+ }
+
/**
* Called when the {@link AutofillManagerService#mFieldClassificationResolver}
* changed (among other places).
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 62a2970..4aeb4a4 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -26,6 +26,7 @@
import android.os.ShellCommand;
import android.os.UserHandle;
import android.service.autofill.AutofillFieldClassificationService.Scores;
+import android.text.TextUtils;
import android.view.autofill.AutofillManager;
import com.android.internal.os.IResultReceiver;
@@ -154,6 +155,8 @@
return getBindInstantService(pw);
case "default-augmented-service-enabled":
return getDefaultAugmentedServiceEnabled(pw);
+ case "field-detection-service-enabled":
+ return isFieldDetectionServiceEnabled(pw);
case "saved-password-count":
return getSavedPasswordCount(pw);
default:
@@ -343,6 +346,14 @@
return 0;
}
+ private int isFieldDetectionServiceEnabled(PrintWriter pw) {
+ final int userId = getNextIntArgRequired();
+ String name = mService.getFieldDetectionServiceName(userId);
+ boolean enabled = !TextUtils.isEmpty(name);
+ pw.println(enabled);
+ return 0;
+ }
+
private int setTemporaryAugmentedService(PrintWriter pw) {
final int userId = getNextIntArgRequired();
final String serviceName = getNextArg();
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c4c1750..192fdfe 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -798,7 +798,7 @@
* Returns empty list if PCC is off or no types available
*/
private List<String> getTypeHintsForProvider() {
- if (!mService.getMaster().isPccClassificationEnabled()) {
+ if (!mService.isPccClassificationEnabled()) {
return Collections.EMPTY_LIST;
}
final String typeHints = mService.getMaster().getPccProviderHints();
@@ -1200,7 +1200,7 @@
// structure is taken. This causes only one fill request per burst of focus changes.
cancelCurrentRequestLocked();
- if (mService.getMaster().isPccClassificationEnabled()
+ if (mService.isPccClassificationEnabled()
&& mClassificationState.mHintsToAutofillIdMap == null) {
if (sVerbose) {
Slog.v(TAG, "triggering field classification");
@@ -1631,7 +1631,7 @@
Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: "
+ autofillProviderContainer);
}
- if (!mService.getMaster().isPccClassificationEnabled()) {
+ if (!mService.isPccClassificationEnabled()) {
if (sVerbose) {
Slog.v(TAG, "PCC classification is disabled");
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index dec0e76..dbeb624 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -29,6 +29,7 @@
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.view.ContextThemeWrapper;
@@ -177,7 +178,14 @@
window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
window.setCloseOnTouchOutside(true);
final WindowManager.LayoutParams params = window.getAttributes();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ window.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ final int screenWidth = displayMetrics.widthPixels;
+ final int maxWidth =
+ mContext.getResources().getDimensionPixelSize(R.dimen.autofill_dialog_max_width);
+ params.width = Math.min(screenWidth, maxWidth);
+
params.accessibilityTitle =
mContext.getString(R.string.autofill_picker_accessibility_title);
params.windowAnimations = R.style.AutofillSaveAnimation;
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 1204259..f035d07 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -48,6 +48,7 @@
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -361,7 +362,14 @@
window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
window.setCloseOnTouchOutside(true);
final WindowManager.LayoutParams params = window.getAttributes();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ window.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ final int screenWidth = displayMetrics.widthPixels;
+ final int maxWidth =
+ context.getResources().getDimensionPixelSize(R.dimen.autofill_dialog_max_width);
+ params.width = Math.min(screenWidth, maxWidth);
+
params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
params.windowAnimations = R.style.AutofillSaveAnimation;
params.setTrustedOverlay();
@@ -563,25 +571,7 @@
private void setServiceIcon(Context context, View view, Drawable serviceIcon) {
final ImageView iconView = view.findViewById(R.id.autofill_save_icon);
final Resources res = context.getResources();
-
- final int maxWidth = res.getDimensionPixelSize(R.dimen.autofill_save_icon_max_size);
- final int maxHeight = maxWidth;
- final int actualWidth = serviceIcon.getMinimumWidth();
- final int actualHeight = serviceIcon.getMinimumHeight();
-
- if (actualWidth <= maxWidth && actualHeight <= maxHeight) {
- if (sDebug) {
- Slog.d(TAG, "Adding service icon "
- + "(" + actualWidth + "x" + actualHeight + ") as it's less than maximum "
- + "(" + maxWidth + "x" + maxHeight + ").");
- }
- iconView.setImageDrawable(serviceIcon);
- } else {
- Slog.w(TAG, "Not adding service icon of size "
- + "(" + actualWidth + "x" + actualHeight + ") because maximum is "
- + "(" + maxWidth + "x" + maxHeight + ").");
- ((ViewGroup)iconView.getParent()).removeView(iconView);
- }
+ iconView.setImageDrawable(serviceIcon);
}
private static boolean isValidLink(PendingIntent pendingIntent, Intent intent) {
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index cb26c13..04db6c0 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -348,21 +348,22 @@
* use caller's BAL permission.
*/
public static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
- @Nullable ActivityOptions activityOptions, int callingUid) {
+ @Nullable ActivityOptions activityOptions, int callingUid,
+ @Nullable String callingPackage) {
if (activityOptions == null) {
// since the ActivityOptions were not created by the app itself, determine the default
// for the app
- return getDefaultBackgroundStartPrivileges(callingUid);
+ return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
}
return getBackgroundStartPrivilegesAllowedByCaller(activityOptions.toBundle(),
- callingUid);
+ callingUid, callingPackage);
}
private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
- @Nullable Bundle options, int callingUid) {
+ @Nullable Bundle options, int callingUid, @Nullable String callingPackage) {
if (options == null || !options.containsKey(
ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
- return getDefaultBackgroundStartPrivileges(callingUid);
+ return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
}
return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
? BackgroundStartPrivileges.ALLOW_BAL
@@ -381,8 +382,10 @@
android.Manifest.permission.LOG_COMPAT_CHANGE
})
public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
- int callingUid) {
- boolean isChangeEnabledForApp = CompatChanges.isChangeEnabled(
+ int callingUid, @Nullable String callingPackage) {
+ boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled(
+ DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage,
+ UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled(
DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingUid);
if (isChangeEnabledForApp) {
return BackgroundStartPrivileges.ALLOW_FGS;
@@ -638,7 +641,7 @@
// temporarily allow receivers and services to open activities from background if the
// PendingIntent.send() caller was foreground at the time of sendInner() call
if (uid != callingUid && controller.mAtmInternal.isUidForeground(callingUid)) {
- return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid);
+ return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid, null);
}
return BackgroundStartPrivileges.NONE;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index b70c3e4..58c7326 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1100,7 +1100,8 @@
synchronized (rolesMap) {
Pair<Integer, Integer> key = new Pair<>(useCase, role);
if (!rolesMap.containsKey(key)) {
- return AudioSystem.SUCCESS;
+ // trying to clear a role for a device that wasn't set
+ return AudioSystem.BAD_VALUE;
}
final int status = asi.deviceRoleAction(useCase, role, null);
if (status == AudioSystem.SUCCESS) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 24eba76..b3fffbf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3373,8 +3373,13 @@
return;
}
- sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
- direction/*val1*/, flags/*val2*/, callingPackage));
+ final VolumeEvent evt = new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+ direction/*val1*/, flags/*val2*/, callingPackage);
+ sVolumeLogger.enqueue(evt);
+ // also logging mute/unmute calls to the dedicated logger
+ if (isMuteAdjust(direction)) {
+ sMuteLogger.enqueue(evt);
+ }
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
callingHasAudioSettingsPermission(), AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
@@ -3475,7 +3480,7 @@
}
// If either the client forces allowing ringer modes for this adjustment,
- // or the stream type is one that is affected by ringer modes
+ // or stream is used for UI sonification
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(isUiSoundsStreamType(streamTypeAlias))) {
int ringerMode = getRingerModeInternal();
@@ -3496,6 +3501,13 @@
if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
}
+ } else if (isStreamMutedByRingerOrZenMode(streamTypeAlias) && streamState.mIsMuted) {
+ // if the stream is currently muted streams by ringer/zen mode
+ // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES)
+ if (direction == AudioManager.ADJUST_TOGGLE_MUTE
+ || direction == AudioManager.ADJUST_UNMUTE) {
+ adjustVolume = false;
+ }
}
// If the ringer mode or zen is muting the stream, do not change stream unless
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 6ad9390..6ebb42e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -609,7 +609,7 @@
(mStreamType <= AudioSystem.getNumStreamTypes() && mStreamType >= 0)
? AudioSystem.STREAM_NAMES[mStreamType]
: ("stream " + mStreamType);
- return new StringBuilder("Error trying to unmute ")
+ return new StringBuilder("Invalid call to unmute ")
.append(streamName)
.append(" despite muted streams 0x")
.append(Integer.toHexString(mRingerZenMutedStreams))
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 5d0177b..c78229b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -205,7 +205,7 @@
@VisibleForTesting
DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy,
- @NonNull SystemPropertySetter systemPropertySetter) {
+ @NonNull SystemPropertySetter systemPropertySetter) {
super(context);
mSystemPropertySetter = systemPropertySetter;
// We use the DisplayThread because this service indirectly drives
@@ -391,12 +391,7 @@
setRearDisplayStateLocked();
- if (!mPendingState.isPresent()) {
- // If the change in the supported states didn't result in a change of the pending
- // state commitPendingState() will never be called and the callbacks will never be
- // notified of the change.
- notifyDeviceStateInfoChangedAsync();
- }
+ notifyDeviceStateInfoChangedAsync();
mHandler.post(this::notifyPolicyIfNeeded);
}
@@ -460,12 +455,7 @@
mOverrideRequestController.handleBaseStateChanged(identifier);
updatePendingStateLocked();
- if (!mPendingState.isPresent()) {
- // If the change in base state didn't result in a change of the pending state
- // commitPendingState() will never be called and the callbacks will never be
- // notified of the change.
- notifyDeviceStateInfoChangedAsync();
- }
+ notifyDeviceStateInfoChangedAsync();
mHandler.post(this::notifyPolicyIfNeeded);
}
@@ -593,6 +583,18 @@
private void notifyDeviceStateInfoChangedAsync() {
synchronized (mLock) {
+ if (mPendingState.isPresent()) {
+ Slog.i(TAG,
+ "Cannot notify device state info change when pending state is present.");
+ return;
+ }
+
+ if (!mBaseState.isPresent() || !mCommittedState.isPresent()) {
+ Slog.e(TAG, "Cannot notify device state info change before the initial state has"
+ + " been committed.");
+ return;
+ }
+
if (mProcessRecords.size() == 0) {
return;
}
@@ -658,7 +660,7 @@
}
} else {
throw new IllegalArgumentException(
- "Unknown OverrideRest type: " + request.getRequestType());
+ "Unknown OverrideRest type: " + request.getRequestType());
}
boolean updatedPendingState = updatePendingStateLocked();
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 1aa1fd1..a3866ca 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2310,6 +2310,9 @@
if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) {
return false;
}
+ if ((intent.getFlags() & Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER) != 0) {
+ return false;
+ }
if (!skipPackageCheck && intent.getPackage() != null) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index a610b5b..affe67d 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -147,7 +147,7 @@
if (launchMainActivity) {
launchIntent.setAction(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- if (targetTask == null) {
+ if (targetTask == null || options != null) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d93778e..fd74dac 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8445,21 +8445,23 @@
private void updateResolvedBoundsPosition(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ if (resolvedBounds.isEmpty()) {
+ return;
+ }
final Rect screenResolvedBounds =
mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- if (resolvedBounds.isEmpty()) {
- return;
- }
+ final float screenResolvedBoundsWidth = screenResolvedBounds.width();
+ final float parentAppBoundsWidth = parentAppBounds.width();
// Horizontal position
int offsetX = 0;
- if (parentBounds.width() != screenResolvedBounds.width()) {
- if (screenResolvedBounds.width() <= parentAppBounds.width()) {
+ if (parentBounds.width() != screenResolvedBoundsWidth) {
+ if (screenResolvedBoundsWidth <= parentAppBoundsWidth) {
float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
newParentConfiguration);
- offsetX = Math.max(0, (int) Math.ceil((parentAppBounds.width()
- - screenResolvedBounds.width()) * positionMultiplier)
+ offsetX = Math.max(0, (int) Math.ceil((parentAppBoundsWidth
+ - screenResolvedBoundsWidth) * positionMultiplier)
// This is added to make sure that insets added inside
// CompatDisplayInsets#getContainerBounds() do not break the alignment
// provided by the positionMultiplier
@@ -8467,14 +8469,21 @@
}
}
+ final float parentAppBoundsHeight = parentAppBounds.height();
+ final float parentBoundsHeight = parentBounds.height();
+ final float screenResolvedBoundsHeight = screenResolvedBounds.height();
// Vertical position
int offsetY = 0;
- if (parentBounds.height() != screenResolvedBounds.height()) {
- if (screenResolvedBounds.height() <= parentAppBounds.height()) {
+ if (parentBoundsHeight != screenResolvedBoundsHeight) {
+ if (screenResolvedBoundsHeight <= parentAppBoundsHeight) {
float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
newParentConfiguration);
- offsetY = Math.max(0, (int) Math.ceil((parentAppBounds.height()
- - screenResolvedBounds.height()) * positionMultiplier)
+ // If in immersive mode, always align to bottom and overlap bottom insets (nav bar,
+ // task bar) as they are transient and hidden. This removes awkward bottom spacing.
+ final float newHeight = mDisplayContent.getDisplayPolicy().isImmersiveMode()
+ ? parentBoundsHeight : parentAppBoundsHeight;
+ offsetY = Math.max(0, (int) Math.ceil((newHeight
+ - screenResolvedBoundsHeight) * positionMultiplier)
// This is added to make sure that insets added inside
// CompatDisplayInsets#getContainerBounds() do not break the alignment
// provided by the positionMultiplier
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index cff6554..b8ae330 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5337,15 +5337,54 @@
return null;
}
- WindowProcessController getProcessController(int pid, int uid) {
+ /**
+ * Returns the {@link WindowProcessController} for the app process for the given uid and pid.
+ *
+ * If no such {@link WindowProcessController} is found, it does not belong to an app, or the
+ * pid does not match the uid {@code null} is returned.
+ */
+ @Nullable WindowProcessController getProcessController(int pid, int uid) {
+ return UserHandle.isApp(uid) ? getProcessControllerInternal(pid, uid) : null;
+ }
+
+ /**
+ * Returns the {@link WindowProcessController} for the given uid and pid.
+ *
+ * If no such {@link WindowProcessController} is found or the pid does not match the uid
+ * {@code null} is returned.
+ */
+ private @Nullable WindowProcessController getProcessControllerInternal(int pid, int uid) {
final WindowProcessController proc = mProcessMap.getProcess(pid);
- if (proc == null) return null;
- if (UserHandle.isApp(uid) && proc.mUid == uid) {
+ if (proc == null) {
+ return null;
+ }
+ if (proc.mUid == uid) {
return proc;
}
return null;
}
+ /**
+ * Returns the package name if (and only if) the package name can be uniquely determined.
+ * Otherwise returns {@code null}.
+ *
+ * The provided pid must match the provided uid, otherwise this also returns null.
+ */
+ @Nullable String getPackageNameIfUnique(int uid, int pid) {
+ WindowProcessController processController = getProcessControllerInternal(pid, uid);
+ if (processController == null) {
+ Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") has no WPC");
+ return null;
+ }
+ List<String> realCallingPackages = processController.getPackageList();
+ if (realCallingPackages.size() == 1) {
+ return realCallingPackages.get(0);
+ }
+ Slog.w(TAG, "callingPackage for (uid=" + uid + ", pid=" + pid + ") is ambiguous: "
+ + realCallingPackages);
+ return null;
+ }
+
/** A uid is considered to be foreground if it has a visible non-toast window. */
@HotPath(caller = HotPath.START_SERVICE)
boolean hasActiveVisibleWindow(int uid) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index dc49e8c..b216578 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -180,7 +180,8 @@
Intent intent,
ActivityOptions checkedOptions) {
return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
- realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
+ realCallingUid, realCallingPid,
+ callerApp, originatingPendingIntent,
backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK;
}
@@ -288,11 +289,13 @@
}
}
+ String realCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
+
// Legacy behavior allows to use caller foreground state to bypass BAL restriction.
// The options here are the options passed by the sender and not those on the intent.
final BackgroundStartPrivileges balAllowedByPiSender =
PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
- checkedOptions, realCallingUid);
+ checkedOptions, realCallingUid, realCallingPackage);
final boolean logVerdictChangeByPiDefaultChange = checkedOptions == null
|| checkedOptions.getPendingIntentBackgroundActivityStartMode()
@@ -460,8 +463,11 @@
// If we are here, it means all exemptions not based on PI sender failed, so we'll block
// unless resultIfPiSenderAllowsBal is an allow and the PI sender allows BAL
- String realCallingPackage = callingUid == realCallingUid ? callingPackage :
- mService.mContext.getPackageManager().getNameForUid(realCallingUid);
+ if (realCallingPackage == null) {
+ realCallingPackage = (callingUid == realCallingUid ? callingPackage :
+ mService.mContext.getPackageManager().getNameForUid(realCallingUid))
+ + "[debugOnly]";
+ }
String stateDumpLog = " [callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 899c36c..77e70a2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1254,6 +1254,10 @@
return mNavigationBar;
}
+ boolean isImmersiveMode() {
+ return mIsImmersiveMode;
+ }
+
/**
* Control the animation to run when a window's state changes. Return a positive number to
* force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index b557b2e..a158e8d2 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1311,6 +1311,16 @@
final Rect cropBounds = new Rect(mActivityRecord.getBounds());
+ // In case of translucent activities we check if the requested size is different from
+ // the size provided using inherited bounds. In that case we decide to not apply rounded
+ // corners because we assume the specific layout would. This is the case when the layout
+ // of the translucent activity uses only a part of all the bounds because of the use of
+ // LayoutParams.WRAP_CONTENT.
+ if (hasInheritedLetterboxBehavior() && (cropBounds.width() != mainWindow.mRequestedWidth
+ || cropBounds.height() != mainWindow.mRequestedHeight)) {
+ return null;
+ }
+
// It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
// because taskbar bounds used in {@link #adjustBoundsIfNeeded}
// are in screen coordinates
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b7c29bf..bb6f805 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5673,6 +5673,7 @@
}
mTransitionController.requestStartTransition(transition, tr,
null /* remoteTransition */, null /* displayChange */);
+ mTransitionController.collect(tr);
moveTaskToBackInner(tr);
});
} else {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index dbd9e4b..3672820 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -721,6 +721,12 @@
}
}
+ List<String> getPackageList() {
+ synchronized (mPkgList) {
+ return new ArrayList<>(mPkgList);
+ }
+ }
+
void addActivityIfNeeded(ActivityRecord r) {
// even if we already track this activity, note down that it has been launched
setLastActivityLaunchTime(r);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 1322225..ee80a05 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -27,6 +27,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.app.BroadcastOptions;
import android.app.admin.DevicePolicyIdentifiers;
import android.app.admin.DevicePolicyManager;
@@ -45,6 +46,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.TelephonyManager;
@@ -1015,20 +1017,44 @@
/**
* Handles internal state related to packages getting updated.
*/
- void handlePackageChanged(@Nullable String updatedPackage, int userId, boolean packageRemoved) {
- if (updatedPackage == null) {
- return;
- }
- if (packageRemoved) {
+ void handlePackageChanged(
+ @Nullable String updatedPackage, int userId, @Nullable String removedDpcPackage) {
+ Binder.withCleanCallingIdentity(() -> {
Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId);
- for (EnforcingAdmin admin : admins) {
- if (admin.getPackageName().equals(updatedPackage)) {
- // remove policies for the uninstalled package
- removePoliciesForAdmin(admin);
+ if (removedDpcPackage != null) {
+ for (EnforcingAdmin admin : admins) {
+ if (removedDpcPackage.equals(admin.getPackageName())) {
+ removePoliciesForAdmin(admin);
+ return;
+ }
}
}
- } else {
- updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
+ for (EnforcingAdmin admin : admins) {
+ if (updatedPackage == null || updatedPackage.equals(admin.getPackageName())) {
+ if (!isPackageInstalled(admin.getPackageName(), userId)) {
+ Slogf.i(TAG, String.format(
+ "Admin package %s not found for user %d, removing admin policies",
+ admin.getPackageName(), userId));
+ // remove policies for the uninstalled package
+ removePoliciesForAdmin(admin);
+ return;
+ }
+ }
+ }
+ if (updatedPackage != null) {
+ updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
+ }
+ });
+ }
+
+ private boolean isPackageInstalled(String packageName, int userId) {
+ try {
+ return AppGlobals.getPackageManager().getPackageInfo(
+ packageName, 0, userId) != null;
+ } catch (RemoteException re) {
+ // Shouldn't happen.
+ Slogf.wtf(TAG, "Error handling package changes", re);
+ return true;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 324e260..8f7e292 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -232,7 +232,6 @@
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
-import static android.provider.DeviceConfig.NAMESPACE_TELEPHONY;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -876,10 +875,6 @@
private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
- private static final String ENABLE_WORK_PROFILE_TELEPHONY_FLAG =
- "enable_work_profile_telephony";
- private static final boolean DEFAULT_WORK_PROFILE_TELEPHONY_FLAG = false;
-
// TODO(b/261999445) remove the flag after rollout.
private static final String HEADLESS_FLAG = "headless";
private static final boolean DEFAULT_HEADLESS_FLAG = true;
@@ -1389,6 +1384,7 @@
private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
boolean removedAdmin = false;
+ String removedAdminPackage = null;
if (VERBOSE_LOG) {
Slogf.d(LOG_TAG, "Handling package changes package " + packageName
+ " for user " + userHandle);
@@ -1411,6 +1407,7 @@
"Admin package %s not found for user %d, removing active admin",
packageName, userHandle));
removedAdmin = true;
+ removedAdminPackage = adminPackage;
policy.mAdminList.remove(i);
policy.mAdminMap.remove(aa.info.getComponent());
pushActiveAdminPackagesLocked(userHandle);
@@ -1444,7 +1441,8 @@
startOwnerService(userHandle, "package-broadcast");
}
if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
- mDevicePolicyEngine.handlePackageChanged(packageName, userHandle, removedAdmin);
+ mDevicePolicyEngine.handlePackageChanged(
+ packageName, userHandle, removedAdminPackage);
}
// Persist updates if the removed package was an admin or delegate.
if (removedAdmin || removedDelegate) {
@@ -3376,9 +3374,7 @@
onLockSettingsReady();
loadAdminDataAsync();
mOwners.systemReady();
- if (isWorkProfileTelephonyEnabled()) {
- applyManagedSubscriptionsPolicyIfRequired();
- }
+ applyManagedSubscriptionsPolicyIfRequired();
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
synchronized (getLockObject()) {
@@ -3409,9 +3405,7 @@
unregisterOnSubscriptionsChangedListener();
int policyType = getManagedSubscriptionsPolicy().getPolicyType();
if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS) {
- final int parentUserId = getProfileParentId(copeProfileUserId);
- // By default, assign all current and future subs to system user on COPE devices.
- registerListenerToAssignSubscriptionsToUser(parentUserId);
+ clearManagedSubscriptionsPolicy();
} else if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
// Add listener to assign all current and future subs to managed profile.
registerListenerToAssignSubscriptionsToUser(copeProfileUserId);
@@ -3541,7 +3535,9 @@
deleteTransferOwnershipBundleLocked(metadata.userId);
}
updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
- pushUserControlDisabledPackagesLocked(metadata.userId);
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
+ pushUserControlDisabledPackagesLocked(metadata.userId);
+ }
}
private void maybeLogStart() {
@@ -7718,11 +7714,10 @@
}
mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
- if (isWorkProfileTelephonyEnabled()) {
- clearManagedSubscriptionsPolicy();
- clearLauncherShortcutOverrides();
- updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
- }
+ clearManagedSubscriptionsPolicy();
+ clearLauncherShortcutOverrides();
+ updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
+
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
}
@@ -10114,7 +10109,9 @@
clearUserPoliciesLocked(userId);
clearOverrideApnUnchecked();
clearApplicationRestrictions(userId);
- mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId);
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
+ mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId);
+ }
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
@@ -10966,8 +10963,13 @@
/* deviceOwnerUserId= */ deviceOwnerUserId, /* callingUserId*/ caller.getUserId(),
isAdb(caller), hasIncompatibleAccountsOrNonAdb);
if (code != STATUS_OK) {
- throw new IllegalStateException(computeProvisioningErrorStringLocked(code,
- deviceOwnerUserId, owner, showComponentOnError));
+ final String provisioningErrorStringLocked = computeProvisioningErrorStringLocked(code,
+ deviceOwnerUserId, owner, showComponentOnError);
+ if (code == STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED) {
+ throw new ServiceSpecificException(code, provisioningErrorStringLocked);
+ } else {
+ throw new IllegalStateException(provisioningErrorStringLocked);
+ }
}
}
@@ -11021,6 +11023,9 @@
case STATUS_HAS_PAIRED:
return "Not allowed to set the device owner because this device has already "
+ "paired.";
+ case STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED:
+ return "Cannot provision an unsupported DPC into DO on a"
+ + " headless device";
default:
return "Unexpected @ProvisioningPreCondition: " + code;
}
@@ -11334,11 +11339,10 @@
synchronized (mSubscriptionsChangedListenerLock) {
pw.println("Subscription changed listener : " + mSubscriptionsChangedListener);
}
- pw.println("DPM Flag enable_work_profile_telephony : "
- + isWorkProfileTelephonyDevicePolicyManagerFlagEnabled());
- pw.println("Telephony Flag enable_work_profile_telephony : "
- + isWorkProfileTelephonySubscriptionManagerFlagEnabled());
+ pw.println("DPM global setting ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS : "
+ + mInjector.settingsGlobalGetString(
+ Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS));
mHandler.post(() -> handleDump(pw));
dumpResources(pw);
}
@@ -11580,6 +11584,15 @@
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller));
}
+
+ if (!parent && isManagedProfile(caller.getUserId())
+ && getManagedSubscriptionsPolicy().getPolicyType()
+ != ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ throw new IllegalStateException(
+ "Default sms application can only be set on the profile, when "
+ + "ManagedSubscriptions policy is set");
+ }
+
if (parent) {
userId = getProfileParentId(mInjector.userHandleGetCallingUserId());
mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -15197,8 +15210,14 @@
@Override
public void setGlobalSetting(ComponentName who, String setting, String value) {
- Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
+ if (Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS.equals(setting)) {
+ Preconditions.checkCallAuthorization(isCallerDevicePolicyManagementRoleHolder(caller));
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.settingsGlobalPutString(setting, value));
+ return;
+ }
+ Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
DevicePolicyEventLogger
@@ -23791,26 +23810,6 @@
suspendAppsForQuietProfiles(keepProfileRunning);
}
- private boolean isWorkProfileTelephonyEnabled() {
- return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
- && isWorkProfileTelephonySubscriptionManagerFlagEnabled();
- }
-
- private boolean isWorkProfileTelephonyDevicePolicyManagerFlagEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_DEVICE_POLICY_MANAGER,
- ENABLE_WORK_PROFILE_TELEPHONY_FLAG, DEFAULT_WORK_PROFILE_TELEPHONY_FLAG);
- }
-
- private boolean isWorkProfileTelephonySubscriptionManagerFlagEnabled() {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- return DeviceConfig.getBoolean(NAMESPACE_TELEPHONY, ENABLE_WORK_PROFILE_TELEPHONY_FLAG,
- false);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
- }
-
@Override
public void setOverrideKeepProfilesRunning(boolean enabled) {
Preconditions.checkCallAuthorization(
@@ -23921,12 +23920,10 @@
@Override
public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
- if (isWorkProfileTelephonyEnabled()) {
- synchronized (getLockObject()) {
- ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
- if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
- return admin.mManagedSubscriptionsPolicy;
- }
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
+ if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
+ return admin.mManagedSubscriptionsPolicy;
}
}
return new ManagedSubscriptionsPolicy(
@@ -23935,10 +23932,14 @@
@Override
public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
- if (!isWorkProfileTelephonyEnabled()) {
+ CallerIdentity caller = getCallerIdentity();
+
+ if (!isCallerDevicePolicyManagementRoleHolder(caller)
+ && !Objects.equals(mInjector.settingsGlobalGetString(
+ Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS), "1")) {
throw new UnsupportedOperationException("This api is not enabled");
}
- CallerIdentity caller = getCallerIdentity();
+
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller),
"This policy can only be set by a profile owner on an organization-owned "
+ "device.");
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 4d06855..4881012 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -88,6 +88,11 @@
mProvider = new TestDeviceStateProvider();
mPolicy = new TestDeviceStatePolicy(mProvider);
mSysPropSetter = new TestSystemPropertySetter();
+ setupDeviceStateManagerService();
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+ }
+
+ private void setupDeviceStateManagerService() {
mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy,
mSysPropSetter);
@@ -97,8 +102,6 @@
when(mService.mActivityTaskManagerInternal.getTopApp())
.thenReturn(mWindowProcessController);
when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
-
- flushHandler(); // Flush the handler to ensure the initial values are committed.
}
private void flushHandler() {
@@ -325,6 +328,21 @@
}
@Test
+ public void registerCallback_initialValueUnavailable() throws RemoteException {
+ // Create a provider and a service without an initial base state.
+ mProvider = new TestDeviceStateProvider(null /* initialState */);
+ mPolicy = new TestDeviceStatePolicy(mProvider);
+ setupDeviceStateManagerService();
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+ flushHandler();
+ // The callback should never be called when the base state is not set yet.
+ assertNull(callback.getLastNotifiedInfo());
+ }
+
+ @Test
public void requestState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
@@ -939,8 +957,18 @@
DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE,
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
+
+ @Nullable private final DeviceState mInitialState;
private Listener mListener;
+ private TestDeviceStateProvider() {
+ this(DEFAULT_DEVICE_STATE);
+ }
+
+ private TestDeviceStateProvider(@Nullable DeviceState initialState) {
+ mInitialState = initialState;
+ }
+
@Override
public void setListener(Listener listener) {
if (mListener != null) {
@@ -950,7 +978,9 @@
mListener = listener;
mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
- mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
+ if (mInitialState != null) {
+ mListener.onStateChanged(mInitialState.getIdentifier());
+ }
}
public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
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 2671e77..2b589bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -944,7 +944,7 @@
anyInt(), anyInt()));
doReturn(BackgroundStartPrivileges.allowBackgroundActivityStarts(null)).when(
() -> PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
- anyObject(), anyInt()));
+ anyObject(), anyInt(), anyObject()));
runAndVerifyBackgroundActivityStartsSubtest(
"allowed_notAborted", false,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 81e0fb3..67e7470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -455,6 +455,29 @@
}
@Test
+ public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
+ final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
+ WindowInsets.Type.navigationBars());
+ taskbar.setInsetsRoundedCornerFrame(true);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+ final Rect opaqueBounds = new Rect(0, 0, 500, 300);
+ doReturn(opaqueBounds).when(mActivity).getBounds();
+ // Activity is translucent
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(mActivity.mLetterboxUiController).hasInheritedLetterboxBehavior();
+
+ // Makes requested sizes different
+ mainWindow.mRequestedWidth = opaqueBounds.width() - 1;
+ mainWindow.mRequestedHeight = opaqueBounds.height() - 1;
+ assertNull(mActivity.mLetterboxUiController.getCropBoundsIfNeeded(mainWindow));
+
+ // Makes requested sizes equals
+ mainWindow.mRequestedWidth = opaqueBounds.width();
+ mainWindow.mRequestedHeight = opaqueBounds.height();
+ assertNotNull(mActivity.mLetterboxUiController.getCropBoundsIfNeeded(mainWindow));
+ }
+
+ @Test
public void testGetCropBoundsIfNeeded_noCrop() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
WindowInsets.Type.navigationBars());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 06bcbf3..2dd34eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3511,6 +3511,51 @@
}
@Test
+ public void testLetterboxAlignedToBottom_NotOverlappingNavbar() {
+ assertLandscapeActivityAlignedToBottomWithNavbar(false /* immersive */);
+ }
+
+ @Test
+ public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() {
+ assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */);
+ }
+
+ private void assertLandscapeActivityAlignedToBottomWithNavbar(boolean immersive) {
+ final int screenHeight = 2800;
+ final int screenWidth = 1400;
+ final int taskbarHeight = 200;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true);
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1.0f);
+
+ final InsetsSource navSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ navSource.setInsetsRoundedCornerFrame(true);
+ // Immersive activity has transient navbar
+ navSource.setVisible(!immersive);
+ navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
+
+ final WindowState w1 = addWindowToActivity(mActivity);
+ w1.mAboveInsetsState.addSource(navSource);
+
+ // Prepare unresizable landscape activity
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+ final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
+ doReturn(immersive).when(displayPolicy).isImmersiveMode();
+
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ LetterboxDetails letterboxDetails = mActivity.mLetterboxUiController.getLetterboxDetails();
+
+ // Letterboxed activity at bottom
+ assertEquals(new Rect(0, 2100, 1400, 2800), mActivity.getBounds());
+ final int expectedHeight = immersive ? screenHeight : screenHeight - taskbarHeight;
+ assertEquals(expectedHeight, letterboxDetails.getLetterboxInnerBounds().bottom);
+ }
+
+ @Test
public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(2800, 1000);
@@ -3938,7 +3983,7 @@
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true);
mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
- 1.0f /*letterboxVerticalPositionMultiplier*/);
+ 1.0f /*letterboxHorizontalPositionMultiplier*/);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
Rect letterboxNoFold = new Rect(2100, 0, 2800, 1400);
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index f9b76f4..9a8c965 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -32,7 +32,6 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.DeviceConfig;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
@@ -262,9 +261,6 @@
*/
public static void showSwitchToManagedProfileDialogIfAppropriate(Context context,
int subId, int callingUid, String callingPackage) {
- if (!isSwitchToManagedProfileDialogFlagEnabled()) {
- return;
- }
final long token = Binder.clearCallingIdentity();
try {
UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
@@ -302,11 +298,6 @@
}
}
- public static boolean isSwitchToManagedProfileDialogFlagEnabled() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
- "enable_switch_to_managed_profile_dialog", false);
- }
-
private static boolean isUidForeground(Context context, int uid) {
ActivityManager am = context.getSystemService(ActivityManager.class);
boolean result = am != null && am.getUidImportance(uid)