diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 051dde01c64e0..b732da29b7544 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -127,6 +127,7 @@ public class UserLifecycleTests {
private BroadcastWaiter mBroadcastWaiter;
private UserSwitchWaiter mUserSwitchWaiter;
private String mUserSwitchTimeoutMs;
+ private String mDisableUserSwitchingDialogAnimations;
private final BenchmarkRunner mRunner = new BenchmarkRunner();
@Rule
@@ -153,16 +154,17 @@ public class UserLifecycleTests {
Log.w(TAG, "WARNING: Tests are being run from user " + mAm.getCurrentUser()
+ " rather than the system user");
}
- mUserSwitchTimeoutMs = setSystemProperty("debug.usercontroller.user_switch_timeout_ms",
- "100000");
- if (TextUtils.isEmpty(mUserSwitchTimeoutMs)) {
- mUserSwitchTimeoutMs = "invalid";
- }
+ mUserSwitchTimeoutMs = setSystemProperty(
+ "debug.usercontroller.user_switch_timeout_ms", "100000");
+ mDisableUserSwitchingDialogAnimations = setSystemProperty(
+ "debug.usercontroller.disable_user_switching_dialog_animations", "true");
}
@After
public void tearDown() throws Exception {
setSystemProperty("debug.usercontroller.user_switch_timeout_ms", mUserSwitchTimeoutMs);
+ setSystemProperty("debug.usercontroller.disable_user_switching_dialog_animations",
+ mDisableUserSwitchingDialogAnimations);
mBroadcastWaiter.close();
mUserSwitchWaiter.close();
for (int userId : mUsersToRemove) {
@@ -1538,7 +1540,7 @@ public class UserLifecycleTests {
private String setSystemProperty(String name, String value) throws Exception {
final String oldValue = ShellHelper.runShellCommand("getprop " + name);
assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value));
- return oldValue;
+ return TextUtils.firstNotEmpty(oldValue, "invalid");
}
private void waitForBroadcastIdle() {
diff --git a/core/res/res/drawable/loading_spinner.xml b/core/res/res/drawable/loading_spinner.xml
new file mode 100644
index 0000000000000..49603d857a1a8
--- /dev/null
+++ b/core/res/res/drawable/loading_spinner.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/res/res/layout/user_switching_dialog.xml b/core/res/res/layout/user_switching_dialog.xml
index 2e041f5f2be2f..496179aefc914 100644
--- a/core/res/res/layout/user_switching_dialog.xml
+++ b/core/res/res/layout/user_switching_dialog.xml
@@ -14,17 +14,48 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+
-
+ android:orientation="vertical"
+ android:paddingBottom="77dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d926c2c7c7a87..a181402e2d9f1 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -141,6 +141,7 @@ import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
* Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -157,7 +158,7 @@ class UserController implements Handler.Callback {
private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
// Amount of time we wait for observers to handle a user switch before
- // giving up on them and unfreezing the screen.
+ // giving up on them and dismissing the user switching dialog.
static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000;
/**
@@ -207,7 +208,7 @@ class UserController implements Handler.Callback {
/**
* Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
* called after dismissing the keyguard.
- * Otherwise, we should move on to unfreeze the screen {@link #unfreezeScreen}
+ * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()}
* and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
*/
private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;
@@ -1695,14 +1696,6 @@ class UserController implements Handler.Callback {
return false;
}
- if (foreground && isUserSwitchUiEnabled()) {
- t.traceBegin("startFreezingScreen");
- mInjector.getWindowManager().startFreezingScreen(
- R.anim.screen_user_exit, R.anim.screen_user_enter);
- t.traceEnd();
- }
- dismissUserSwitchDialog(); // so that we don't hold a reference to mUserSwitchingDialog
-
boolean needStart = false;
boolean updateUmState = false;
UserState uss;
@@ -1877,7 +1870,7 @@ class UserController implements Handler.Callback {
if (!success) {
mInjector.getWindowManager().setSwitchingUser(false);
mTargetUserId = UserHandle.USER_NULL;
- dismissUserSwitchDialog();
+ dismissUserSwitchDialog(null);
}
}
@@ -2015,22 +2008,26 @@ class UserController implements Handler.Callback {
mUiHandler.sendMessage(mUiHandler.obtainMessage(
START_USER_SWITCH_UI_MSG, userNames));
} else {
- mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(
- START_USER_SWITCH_FG_MSG, targetUserId, 0));
+ sendStartUserSwitchFgMessage(targetUserId);
}
return true;
}
- private void dismissUserSwitchDialog() {
- mInjector.dismissUserSwitchingDialog();
+ private void sendStartUserSwitchFgMessage(int targetUserId) {
+ mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_FG_MSG, targetUserId, 0));
+ }
+
+ private void dismissUserSwitchDialog(Runnable onDismissed) {
+ mInjector.dismissUserSwitchingDialog(onDismissed);
}
private void showUserSwitchDialog(Pair fromToUserPair) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
getSwitchingFromSystemUserMessageUnchecked(),
- getSwitchingToSystemUserMessageUnchecked());
+ getSwitchingToSystemUserMessageUnchecked(),
+ /* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id));
}
private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
@@ -2236,7 +2233,7 @@ class UserController implements Handler.Callback {
EventLog.writeEvent(EventLogTags.UC_CONTINUE_USER_SWITCH, oldUserId, newUserId);
- // Do the keyguard dismiss and unfreeze later
+ // Do the keyguard dismiss and dismiss the user switching dialog later
mHandler.removeMessages(COMPLETE_USER_SWITCH_MSG);
mHandler.sendMessage(mHandler.obtainMessage(
COMPLETE_USER_SWITCH_MSG, oldUserId, newUserId));
@@ -2251,35 +2248,31 @@ class UserController implements Handler.Callback {
@VisibleForTesting
void completeUserSwitch(int oldUserId, int newUserId) {
final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
- final Runnable runnable = () -> {
- if (isUserSwitchUiEnabled) {
- unfreezeScreen();
- }
- mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(
- REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
- };
-
- // If there is no challenge set, dismiss the keyguard right away
- if (isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
- // Wait until the keyguard is dismissed to unfreeze
- mInjector.dismissKeyguard(runnable);
- } else {
- runnable.run();
- }
+ // serialize each conditional step
+ await(
+ // STEP 1 - If there is no challenge set, dismiss the keyguard right away
+ isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId),
+ mInjector::dismissKeyguard,
+ () -> await(
+ // STEP 2 - If user switch ui was enabled, dismiss user switch dialog
+ isUserSwitchUiEnabled,
+ this::dismissUserSwitchDialog,
+ () -> {
+ // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast
+ // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete
+ mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
+ }
+ ));
}
- /**
- * Tell WindowManager we're ready to unfreeze the screen, at its leisure. Note that there is
- * likely a lot going on, and WM won't unfreeze until the drawing is all done, so
- * the actual unfreeze may still not happen for a long time; this is expected.
- */
- @VisibleForTesting
- void unfreezeScreen() {
- TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("stopFreezingScreen");
- mInjector.getWindowManager().stopFreezingScreen();
- t.traceEnd();
+ private void await(boolean condition, Consumer conditionalStep, Runnable nextStep) {
+ if (condition) {
+ conditionalStep.accept(nextStep);
+ } else {
+ nextStep.run();
+ }
}
private void moveUserToForeground(UserState uss, int newUserId) {
@@ -3731,17 +3724,18 @@ class UserController implements Handler.Callback {
mService.mCpHelper.installEncryptionUnawareProviders(userId);
}
- void dismissUserSwitchingDialog() {
+ void dismissUserSwitchingDialog(@Nullable Runnable onDismissed) {
synchronized (mUserSwitchingDialogLock) {
if (mUserSwitchingDialog != null) {
- mUserSwitchingDialog.dismiss();
+ mUserSwitchingDialog.dismiss(onDismissed);
mUserSwitchingDialog = null;
}
}
}
void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
- String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+ String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+ @NonNull Runnable onShown) {
if (mService.mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
// config_customUserSwitchUi is set to true on Automotive as CarSystemUI is
@@ -3751,11 +3745,10 @@ class UserController implements Handler.Callback {
+ "condition if it's shown by CarSystemUI as well");
}
synchronized (mUserSwitchingDialogLock) {
- dismissUserSwitchingDialog();
- mUserSwitchingDialog = new UserSwitchingDialog(mService, mService.mContext,
- fromUser, toUser, true /* above system */, switchingFromSystemUserMessage,
- switchingToSystemUserMessage);
- mUserSwitchingDialog.show();
+ dismissUserSwitchingDialog(null);
+ mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
+ switchingFromSystemUserMessage, switchingToSystemUserMessage);
+ mUserSwitchingDialog.show(onShown);
}
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index a5651bfa3dde2..649305f7ee015 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -16,160 +16,258 @@
package com.android.server.am;
-import android.app.AlertDialog;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.Dialog;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Message;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-import android.view.LayoutInflater;
+import android.util.TypedValue;
import android.view.View;
-import android.view.ViewTreeObserver;
+import android.view.Window;
import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ObjectUtils;
+import com.android.internal.util.UserIcons;
/**
- * Dialog to show when a user switch it about to happen. The intent is to snapshot the screen
- * immediately after the dialog shows so that the user is informed that something is happening
- * in the background rather than just freeze the screen and not know if the user-switch affordance
- * was being handled.
+ * Dialog to show during the user switch. This dialog shows target user's name and their profile
+ * picture with a circular spinner animation around it if the animations for this dialog are not
+ * disabled. And covers the whole screen so that all the UI jank caused by the switch are hidden.
*/
-class UserSwitchingDialog extends AlertDialog
- implements ViewTreeObserver.OnWindowShownListener {
- private static final String TAG = "ActivityManagerUserSwitchingDialog";
-
- // Time to wait for the onWindowShown() callback before continuing the user switch
- private static final int WINDOW_SHOWN_TIMEOUT_MS = 3000;
+class UserSwitchingDialog extends Dialog {
+ private static final String TAG = "UserSwitchingDialog";
+ private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
// User switching doesn't happen that frequently, so it doesn't hurt to have it always on
protected static final boolean DEBUG = true;
+ private static final long DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS = 300;
+ private final boolean mDisableAnimations;
- private final ActivityManagerService mService;
- private final int mUserId;
- private static final int MSG_START_USER = 1;
- @GuardedBy("this")
- private boolean mStartedUser;
- final protected UserInfo mOldUser;
- final protected UserInfo mNewUser;
- final private String mSwitchingFromSystemUserMessage;
- final private String mSwitchingToSystemUserMessage;
- final protected Context mContext;
+ protected final UserInfo mOldUser;
+ protected final UserInfo mNewUser;
+ private final String mSwitchingFromSystemUserMessage;
+ private final String mSwitchingToSystemUserMessage;
+ protected final Context mContext;
+ private final int mTraceCookie;
- public UserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
- UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage,
- String switchingToSystemUserMessage) {
- super(context);
+ UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser,
+ String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
+ // TODO(b/278857848): Make full screen user switcher cover top part of the screen as well.
+ // This problem is seen only on phones, it works fine on tablets.
+ super(context, R.style.Theme_Material_NoActionBar_Fullscreen);
mContext = context;
- mService = service;
- mUserId = newUser.id;
mOldUser = oldUser;
mNewUser = newUser;
mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
+ mDisableAnimations = ActivityManager.isLowRamDeviceStatic() || SystemProperties.getBoolean(
+ "debug.usercontroller.disable_user_switching_dialog_animations", false);
+ mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id;
inflateContent();
+ configureWindow();
+ }
- if (aboveSystem) {
- getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
- }
-
- WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ private void configureWindow() {
+ final Window window = getWindow();
+ final WindowManager.LayoutParams attrs = window.getAttributes();
attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
- WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- getWindow().setAttributes(attrs);
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ window.setAttributes(attrs);
+ window.setBackgroundDrawableResource(android.R.color.transparent);
+ window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
}
void inflateContent() {
- // Set up the dialog contents
setCancelable(false);
- Resources res = getContext().getResources();
- // Custom view due to alignment and font size requirements
- TextView view = (TextView) LayoutInflater.from(getContext()).inflate(
- R.layout.user_switching_dialog, null);
+ setContentView(R.layout.user_switching_dialog);
- String viewMessage = null;
- if (UserManager.isDeviceInDemoMode(mContext)) {
- if (mOldUser.isDemo()) {
- viewMessage = res.getString(R.string.demo_restarting_message);
- } else {
- viewMessage = res.getString(R.string.demo_starting_message);
- }
- } else {
- if (mOldUser.id == UserHandle.USER_SYSTEM) {
- viewMessage = mSwitchingFromSystemUserMessage;
- } else if (mNewUser.id == UserHandle.USER_SYSTEM) {
- viewMessage = mSwitchingToSystemUserMessage;
- }
-
- // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null, fallback
- // to system message.
- if (viewMessage == null) {
- viewMessage = res.getString(R.string.user_switching_message, mNewUser.name);
- }
-
- view.setCompoundDrawablesWithIntrinsicBounds(null,
- getContext().getDrawable(R.drawable.ic_swap_horiz), null, null);
+ final TextView textView = findViewById(R.id.message);
+ if (textView != null) {
+ final String message = getTextMessage();
+ textView.setAccessibilityPaneTitle(message);
+ textView.setText(message);
}
- view.setAccessibilityPaneTitle(viewMessage);
- view.setText(viewMessage);
- setView(view);
+
+ final ImageView imageView = findViewById(R.id.icon);
+ if (imageView != null) {
+ imageView.setImageBitmap(getUserIconRounded());
+ }
+
+ final ImageView progressCircular = findViewById(R.id.progress_circular);
+ if (progressCircular != null) {
+ if (mDisableAnimations) {
+ progressCircular.setVisibility(View.GONE);
+ } else {
+ final TypedValue value = new TypedValue();
+ getContext().getTheme().resolveAttribute(R.attr.colorAccentPrimary, value, true);
+ progressCircular.setColorFilter(value.data);
+ }
+ }
+ }
+
+ private Bitmap getUserIconRounded() {
+ final Bitmap bmp = ObjectUtils.getOrElse(BitmapFactory.decodeFile(mNewUser.iconPath),
+ defaultUserIcon(mNewUser.id));
+ final int w = bmp.getWidth();
+ final int h = bmp.getHeight();
+ final Bitmap bmpRounded = Bitmap.createBitmap(w, h, bmp.getConfig());
+ final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setShader(new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+ new Canvas(bmpRounded).drawRoundRect((new RectF(0, 0, w, h)), w / 2f, h / 2f, paint);
+ return bmpRounded;
+ }
+
+ private Bitmap defaultUserIcon(@UserIdInt int userId) {
+ final Resources res = getContext().getResources();
+ final Drawable icon = UserIcons.getDefaultUserIcon(res, userId, /* light= */ false);
+ return UserIcons.convertToBitmapAtUserIconSize(res, icon);
+ }
+
+ private String getTextMessage() {
+ final Resources res = getContext().getResources();
+
+ if (UserManager.isDeviceInDemoMode(mContext)) {
+ return res.getString(mOldUser.isDemo()
+ ? R.string.demo_restarting_message
+ : R.string.demo_starting_message);
+ }
+
+ final String message =
+ mOldUser.id == UserHandle.USER_SYSTEM ? mSwitchingFromSystemUserMessage
+ : mNewUser.id == UserHandle.USER_SYSTEM ? mSwitchingToSystemUserMessage : null;
+
+ return message != null ? message
+ // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null,
+ // fallback to system message.
+ : res.getString(R.string.user_switching_message, mNewUser.name);
}
@Override
public void show() {
- if (DEBUG) Slog.d(TAG, "show called");
+ asyncTraceBegin("", 0);
super.show();
- final View decorView = getWindow().getDecorView();
- if (decorView != null) {
- decorView.getViewTreeObserver().addOnWindowShownListener(this);
- }
- // Add a timeout as a safeguard, in case a race in screen on/off causes the window
- // callback to never come.
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_USER),
- WINDOW_SHOWN_TIMEOUT_MS);
}
@Override
- public void onWindowShown() {
- if (DEBUG) Slog.d(TAG, "onWindowShown called");
- startUser();
+ public void dismiss() {
+ super.dismiss();
+ asyncTraceEnd("", 0);
}
- void startUser() {
- synchronized (this) {
- if (!mStartedUser) {
- Slog.i(TAG, "starting user " + mUserId);
- mService.mUserController.startUserInForeground(mUserId);
+ public void show(@NonNull Runnable onShown) {
+ if (DEBUG) Slog.d(TAG, "show called");
+ show();
+
+ if (mDisableAnimations) {
+ onShown.run();
+ } else {
+ startShowAnimation(onShown);
+ }
+ }
+
+ public void dismiss(@Nullable Runnable onDismissed) {
+ if (DEBUG) Slog.d(TAG, "dismiss called");
+
+ if (onDismissed == null) {
+ // no animation needed
+ dismiss();
+ } else if (mDisableAnimations) {
+ dismiss();
+ onDismissed.run();
+ } else {
+ startDismissAnimation(() -> {
dismiss();
- mStartedUser = true;
- final View decorView = getWindow().getDecorView();
- if (decorView != null) {
- decorView.getViewTreeObserver().removeOnWindowShownListener(this);
- }
- mHandler.removeMessages(MSG_START_USER);
- } else {
- Slog.i(TAG, "user " + mUserId + " already started");
- }
+ onDismissed.run();
+ });
}
}
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_START_USER:
- Slog.w(TAG, "user switch window not shown in "
- + WINDOW_SHOWN_TIMEOUT_MS + " ms");
- startUser();
- break;
+ private void startShowAnimation(Runnable onAnimationEnd) {
+ asyncTraceBegin("-showAnimation", 1);
+ startDialogAnimation(new AlphaAnimation(0, 1), () -> {
+ asyncTraceEnd("-showAnimation", 1);
+
+ asyncTraceBegin("-spinnerAnimation", 2);
+ startProgressAnimation(() -> {
+ asyncTraceEnd("-spinnerAnimation", 2);
+
+ onAnimationEnd.run();
+ });
+ });
+ }
+
+ private void startDismissAnimation(Runnable onAnimationEnd) {
+ asyncTraceBegin("-dismissAnimation", 3);
+ startDialogAnimation(new AlphaAnimation(1, 0), () -> {
+ asyncTraceEnd("-dismissAnimation", 3);
+
+ onAnimationEnd.run();
+ });
+ }
+
+ private void startProgressAnimation(Runnable onAnimationEnd) {
+ final ImageView progressCircular = findViewById(R.id.progress_circular);
+ final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) progressCircular.getDrawable();
+ avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ onAnimationEnd.run();
}
- }
- };
+ });
+ avd.start();
+ }
+
+ private void startDialogAnimation(Animation animation, Runnable onAnimationEnd) {
+ animation.setDuration(DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS);
+ animation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ onAnimationEnd.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+ });
+ findViewById(R.id.content).startAnimation(animation);
+ }
+
+ private void asyncTraceBegin(String subTag, int subCookie) {
+ Trace.asyncTraceBegin(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
+ }
+
+ private void asyncTraceEnd(String subTag, int subCookie) {
+ Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index ab8f3f2279fe5..d12741ac8bd67 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -211,8 +211,7 @@ public class UserControllerTest {
@Test
public void testStartUser_foreground() {
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
- verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt());
- verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+ verify(mInjector, never()).dismissUserSwitchingDialog(any());
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
verify(mInjector.getWindowManager()).setSwitchingUser(true);
verify(mInjector).clearAllLockedTasks(anyString());
@@ -224,7 +223,8 @@ public class UserControllerTest {
public void testStartUser_background() {
boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
- verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+ verify(mInjector, never()).showUserSwitchingDialog(
+ any(), any(), anyString(), anyString(), any());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
startBackgroundUserAssertions();
@@ -276,7 +276,8 @@ public class UserControllerTest {
assertWithMessage("startUserOnDisplay(%s, %s)", TEST_USER_ID, 42).that(started).isTrue();
verifyUserAssignedToDisplay(TEST_USER_ID, 42);
- verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+ verify(mInjector, never()).showUserSwitchingDialog(
+ any(), any(), anyString(), anyString(), any());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
startBackgroundUserAssertions();
@@ -288,8 +289,9 @@ public class UserControllerTest {
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
- verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
- verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+ verify(mInjector, never()).showUserSwitchingDialog(
+ any(), any(), anyString(), anyString(), any());
+ verify(mInjector, never()).dismissUserSwitchingDialog(any());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
startForegroundUserAssertions();
}
@@ -310,7 +312,8 @@ public class UserControllerTest {
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
- verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
+ verify(mInjector, never()).showUserSwitchingDialog(
+ any(), any(), anyString(), anyString(), any());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
@@ -442,7 +445,7 @@ public class UserControllerTest {
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(0)).dismissKeyguard(any());
- verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
+ verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@@ -463,7 +466,7 @@ public class UserControllerTest {
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(1)).dismissKeyguard(any());
- verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
+ verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@@ -483,7 +486,7 @@ public class UserControllerTest {
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
+ verify(mInjector, never()).dismissUserSwitchingDialog(any());
continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
}
@@ -985,8 +988,7 @@ public class UserControllerTest {
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- verify(mInjector.getWindowManager(), times(expectedNumberOfCalls))
- .stopFreezingScreen();
+ verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any());
continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
}
@@ -1188,6 +1190,22 @@ public class UserControllerTest {
runnable.run();
}
+ @Override
+ void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
+ String switchingFromSystemUserMessage, String switchingToSystemUserMessage,
+ Runnable onShown) {
+ if (onShown != null) {
+ onShown.run();
+ }
+ }
+
+ @Override
+ void dismissUserSwitchingDialog(Runnable onDismissed) {
+ if (onDismissed != null) {
+ onDismissed.run();
+ }
+ }
+
@Override
protected LockPatternUtils getLockPatternUtils() {
return mLockPatternUtilsMock;