diff options
171 files changed, 7707 insertions, 4893 deletions
diff --git a/Android.bp b/Android.bp index b20b08552cec..9d05ffd3de8c 100644 --- a/Android.bp +++ b/Android.bp @@ -248,10 +248,6 @@ java_library { name: "framework-minus-apex", defaults: ["framework-defaults"], javac_shard_size: 150, - required: [ - "framework-platform-compat-config", - "libcore-platform-compat-config", - ], } java_library { @@ -262,6 +258,10 @@ java_library { "framework-minus-apex", "jobscheduler-framework", ], + required: [ + "framework-platform-compat-config", + "libcore-platform-compat-config", + ], sdk_version: "core_platform", } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java index 4c11947212f9..1bb9e967c025 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java @@ -18,13 +18,20 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; +import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.Settings; +import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; @@ -32,6 +39,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.job.ConstantsProto; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; @@ -55,6 +63,9 @@ public final class TimeController extends StateController { /** Delay alarm tag for logging purposes */ private final String DELAY_TAG = "*job.delay*"; + private final Handler mHandler; + private final TcConstants mTcConstants; + private long mNextJobExpiredElapsedMillis; private long mNextDelayExpiredElapsedMillis; @@ -70,6 +81,14 @@ public final class TimeController extends StateController { mNextJobExpiredElapsedMillis = Long.MAX_VALUE; mNextDelayExpiredElapsedMillis = Long.MAX_VALUE; mChainedAttributionEnabled = mService.isChainedAttributionEnabled(); + + mHandler = new Handler(mContext.getMainLooper()); + mTcConstants = new TcConstants(mHandler); + } + + @Override + public void onSystemServicesReady() { + mTcConstants.start(mContext.getContentResolver()); } /** @@ -294,8 +313,7 @@ public final class TimeController extends StateController { } else { if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) { if (DEBUG) { - Slog.i(TAG, - "Skipping " + job + " because delay won't make it ready."); + Slog.i(TAG, "Skipping " + job + " because delay won't make it ready."); } continue; } @@ -354,7 +372,8 @@ public final class TimeController extends StateController { /** * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's * delay will expire. - * This alarm <b>will</b> wake up the phone. + * This alarm <b>will not</b> wake up the phone if + * {@link TcConstants#USE_NON_WAKEUP_ALARM_FOR_DELAY} is true. */ private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); @@ -362,8 +381,11 @@ public final class TimeController extends StateController { return; } mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis; - updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener, - mNextDelayExpiredElapsedMillis, ws); + final int alarmType = + mTcConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY + ? AlarmManager.ELAPSED_REALTIME : AlarmManager.ELAPSED_REALTIME_WAKEUP; + updateAlarmWithListenerLocked(DELAY_TAG, alarmType, + mNextDelayExpiredListener, mNextDelayExpiredElapsedMillis, ws); } /** @@ -377,16 +399,16 @@ public final class TimeController extends StateController { return; } mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis; - updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener, - mNextJobExpiredElapsedMillis, ws); + updateAlarmWithListenerLocked(DEADLINE_TAG, AlarmManager.ELAPSED_REALTIME_WAKEUP, + mDeadlineExpiredListener, mNextJobExpiredElapsedMillis, ws); } private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) { return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis()); } - private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener, - long alarmTimeElapsed, WorkSource ws) { + private void updateAlarmWithListenerLocked(String tag, @AlarmManager.AlarmType int alarmType, + OnAlarmListener listener, long alarmTimeElapsed, WorkSource ws) { ensureAlarmServiceLocked(); if (alarmTimeElapsed == Long.MAX_VALUE) { mAlarmService.cancel(listener); @@ -394,7 +416,7 @@ public final class TimeController extends StateController { if (DEBUG) { Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed); } - mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed, + mAlarmService.set(alarmType, alarmTimeElapsed, AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws); } } @@ -422,9 +444,77 @@ public final class TimeController extends StateController { }; @VisibleForTesting - void recheckAlarmsLocked() { - checkExpiredDeadlinesAndResetAlarm(); - checkExpiredDelaysAndResetAlarm(); + class TcConstants extends ContentObserver { + private ContentResolver mResolver; + private final KeyValueListParser mParser = new KeyValueListParser(','); + + private static final String KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY = + "use_non_wakeup_delay_alarm"; + + private static final boolean DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY = true; + + /** + * Whether or not TimeController should skip setting wakeup alarms for jobs that aren't + * ready now. + */ + public boolean USE_NON_WAKEUP_ALARM_FOR_DELAY = DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY; + + /** + * Creates a content observer. + * + * @param handler The handler to run {@link #onChange} on, or null if none. + */ + TcConstants(Handler handler) { + super(handler); + } + + private void start(ContentResolver resolver) { + mResolver = resolver; + mResolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS), false, this); + onChange(true, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + final String constants = Settings.Global.getString( + mResolver, Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS); + + try { + mParser.setString(constants); + } catch (Exception e) { + // Failed to parse the settings string, log this and move on with defaults. + Slog.e(TAG, "Bad jobscheduler time controller settings", e); + } + + USE_NON_WAKEUP_ALARM_FOR_DELAY = mParser.getBoolean( + KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY, DEFAULT_USE_NON_WAKEUP_ALARM_FOR_DELAY); + // Intentionally not calling checkExpiredDelaysAndResetAlarm() here. There's no need to + // iterate through the entire list again for this constant change. The next delay alarm + // that is set will make use of the new constant value. + } + + private void dump(IndentingPrintWriter pw) { + pw.println(); + pw.println("TimeController:"); + pw.increaseIndent(); + pw.printPair(KEY_USE_NON_WAKEUP_ALARM_FOR_DELAY, + USE_NON_WAKEUP_ALARM_FOR_DELAY).println(); + pw.decreaseIndent(); + } + + private void dump(ProtoOutputStream proto) { + final long tcToken = proto.start(ConstantsProto.TIME_CONTROLLER); + proto.write(ConstantsProto.TimeController.USE_NON_WAKEUP_ALARM_FOR_DELAY, + USE_NON_WAKEUP_ALARM_FOR_DELAY); + proto.end(tcToken); + } + } + + @VisibleForTesting + @NonNull + TcConstants getTcConstants() { + return mTcConstants; } @Override @@ -501,4 +591,14 @@ public final class TimeController extends StateController { proto.end(mToken); proto.end(token); } + + @Override + public void dumpConstants(IndentingPrintWriter pw) { + mTcConstants.dump(pw); + } + + @Override + public void dumpConstants(ProtoOutputStream proto) { + mTcConstants.dump(proto); + } } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index ed2b99187b95..f9b96c50e0b8 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -242,7 +242,7 @@ public final class UiAutomation { mUiAutomationConnection.connect(mClient, flags); mFlags = flags; } catch (RemoteException re) { - throw new RuntimeException("Error while connecting UiAutomation", re); + throw new RuntimeException("Error while connecting " + this, re); } synchronized (mLock) { @@ -255,7 +255,7 @@ public final class UiAutomation { final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis; if (remainingTimeMillis <= 0) { - throw new RuntimeException("Error while connecting UiAutomation"); + throw new RuntimeException("Error while connecting " + this); } try { mLock.wait(remainingTimeMillis); @@ -290,7 +290,7 @@ public final class UiAutomation { synchronized (mLock) { if (mIsConnecting) { throw new IllegalStateException( - "Cannot call disconnect() while connecting!"); + "Cannot call disconnect() while connecting " + this); } throwIfNotConnectedLocked(); mConnectionId = CONNECTION_ID_UNDEFINED; @@ -299,7 +299,7 @@ public final class UiAutomation { // Calling out without a lock held. mUiAutomationConnection.disconnect(); } catch (RemoteException re) { - throw new RuntimeException("Error while disconnecting UiAutomation", re); + throw new RuntimeException("Error while disconnecting " + this, re); } finally { mRemoteCallbackThread.quit(); mRemoteCallbackThread = null; @@ -1184,19 +1184,29 @@ public final class UiAutomation { return result; } + @Override + public String toString() { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode())); + stringBuilder.append("[id=").append(mConnectionId); + stringBuilder.append(", flags=").append(mFlags); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + private boolean isConnectedLocked() { return mConnectionId != CONNECTION_ID_UNDEFINED; } private void throwIfConnectedLocked() { if (mConnectionId != CONNECTION_ID_UNDEFINED) { - throw new IllegalStateException("UiAutomation not connected!"); + throw new IllegalStateException("UiAutomation not connected, " + this); } } private void throwIfNotConnectedLocked() { if (!isConnectedLocked()) { - throw new IllegalStateException("UiAutomation not connected!"); + throw new IllegalStateException("UiAutomation not connected, " + this); } } @@ -1220,6 +1230,9 @@ public final class UiAutomation { mConnectionId = connectionId; mLock.notifyAll(); } + if (Build.IS_DEBUGGABLE) { + Log.v(LOG_TAG, "Init " + UiAutomation.this); + } } @Override diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 4ea3726bee6d..f297c0631a30 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -17,6 +17,7 @@ package android.content; import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_ERRORED; @@ -645,9 +646,11 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } boolean checkUser(int pid, int uid, Context context) { - return UserHandle.getUserId(uid) == context.getUserId() - || mSingleUser - || context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) + if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) { + return true; + } + return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid) == PERMISSION_GRANTED + || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) == PERMISSION_GRANTED; } @@ -1030,10 +1033,12 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall /** @hide */ public final void setTransportLoggingEnabled(boolean enabled) { - if (enabled) { - mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this); - } else { - mTransport.mInterface = this; + if (mTransport != null) { + if (enabled) { + mTransport.mInterface = new LoggingContentInterface(getClass().getSimpleName(), this); + } else { + mTransport.mInterface = this; + } } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index bb5ced56ec59..2c53faa5c890 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -38,6 +38,7 @@ import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; +import android.compat.IPlatformCompat; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -3228,6 +3229,7 @@ public abstract class Context { ROLE_SERVICE, //@hide ROLE_CONTROLLER_SERVICE, CAMERA_SERVICE, + //@hide: PLATFORM_COMPAT_SERVICE, PRINT_SERVICE, CONSUMER_IR_SERVICE, //@hide: TRUST_SERVICE, @@ -4597,6 +4599,13 @@ public abstract class Context { public static final String STATS_MANAGER = "stats"; /** + * Use with {@link android.os.ServiceManager.getService()} to retrieve a + * {@link IPlatformCompat} IBinder for communicating with the platform compat service. + * @hide + */ + public static final String PLATFORM_COMPAT_SERVICE = "platform_compat"; + + /** * Service to capture a bugreport. * @see #getSystemService(String) * @see android.os.BugreportManager diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java index 73b8a48d9153..1609f53d3d3b 100644 --- a/core/java/android/content/rollback/RollbackManager.java +++ b/core/java/android/content/rollback/RollbackManager.java @@ -74,7 +74,10 @@ public final class RollbackManager { } /** - * Returns a list of all currently available rollbacks. + * Returns a list of all currently available rollbacks. This includes ones for very recently + * installed packages (even if onFinished has not yet been called). As a result, packages that + * very recently failed to install may also be included, but those rollbacks will fail with + * 'rollback not available'. * * @throws SecurityException if the caller does not have appropriate permissions. */ diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 1142a07bc66c..fb6b231632f1 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -100,9 +100,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ - public static final int DISMISSED_REASON_POSITIVE = 1; + public static final int DISMISSED_REASON_CONFIRMED = 1; /** + * Dialog is done animating away after user clicked on the button set via + * {@link BiometricPrompt.Builder#setNegativeButton(CharSequence, Executor, + * DialogInterface.OnClickListener)}. * @hide */ public static final int DISMISSED_REASON_NEGATIVE = 2; @@ -112,6 +115,25 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan */ public static final int DISMISSED_REASON_USER_CANCEL = 3; + /** + * Authenticated, confirmation not required. Dialog animated away. + * @hide + */ + public static final int DISMISSED_REASON_CONFIRM_NOT_REQUIRED = 4; + + /** + * Error message shown on SystemUI. When BiometricService receives this, the UI is already + * gone. + * @hide + */ + public static final int DISMISSED_REASON_ERROR = 5; + + /** + * Dialog dismissal requested by BiometricService. + * @hide + */ + public static final int DISMISSED_REASON_SERVER_REQUESTED = 6; + private static class ButtonInfo { Executor executor; DialogInterface.OnClickListener listener; @@ -362,7 +384,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan @Override public void onDialogDismissed(int reason) throws RemoteException { // Check the reason and invoke OnClickListener(s) if necessary - if (reason == DISMISSED_REASON_POSITIVE) { + if (reason == DISMISSED_REASON_CONFIRMED) { mPositiveButtonInfo.executor.execute(() -> { mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); }); diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl index 180daaf97ada..ca6114e4d842 100644 --- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl +++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl @@ -27,8 +27,8 @@ oneway interface IBiometricServiceReceiverInternal { // Notify BiometricService that authentication was successful. If user confirmation is required, // the auth token must be submitted into KeyStore. void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token); - // Notify BiometricService that an error has occurred. - void onAuthenticationFailed(int cookie, boolean requireConfirmation); + // Notify BiometricService authentication was rejected. + void onAuthenticationFailed(); // Notify BiometricService than an error has occured. Forward to the correct receiver depending // on the cookie. void onError(int cookie, int error, String message); diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index 0fb93e532cd3..beff0f7607f8 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -211,10 +211,12 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { @Override public void onProgramListUpdated(ProgramList.Chunk chunk) { - synchronized (mLock) { - if (mProgramList == null) return; - mProgramList.apply(Objects.requireNonNull(chunk)); - } + mHandler.post(() -> { + synchronized (mLock) { + if (mProgramList == null) return; + mProgramList.apply(Objects.requireNonNull(chunk)); + } + }); } @Override diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index 0513feef801f..356b3448430a 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.app.Dialog; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; @@ -50,6 +51,7 @@ public class SoftInputWindow extends Dialog { final int mWindowType; final int mGravity; final boolean mTakesFocus; + final boolean mAutomotiveHideNavBarForKeyboard; private final Rect mBounds = new Rect(); @Retention(SOURCE) @@ -134,6 +136,8 @@ public class SoftInputWindow extends Dialog { mWindowType = windowType; mGravity = gravity; mTakesFocus = takesFocus; + mAutomotiveHideNavBarForKeyboard = context.getResources().getBoolean( + com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard); initDockWindow(); } @@ -247,6 +251,11 @@ public class SoftInputWindow extends Dialog { windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; } + if (isAutomotive() && mAutomotiveHideNavBarForKeyboard) { + windowSetFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + windowModFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + } + getWindow().setFlags(windowSetFlags, windowModFlags); } @@ -338,6 +347,10 @@ public class SoftInputWindow extends Dialog { mWindowState = newState; } + private boolean isAutomotive() { + return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + private static String stateToString(@SoftInputWindowState int state) { switch (state) { case SoftInputWindowState.TOKEN_PENDING: diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java index e4f88c52889f..77fd946f7ccb 100644 --- a/core/java/android/os/image/DynamicSystemManager.java +++ b/core/java/android/os/image/DynamicSystemManager.java @@ -20,6 +20,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.gsi.GsiProgress; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; /** @@ -52,22 +53,39 @@ public class DynamicSystemManager { /** The DynamicSystemManager.Session represents a started session for the installation. */ public class Session { private Session() {} + /** - * Write a chunk of the DynamicSystem system image + * Set the file descriptor that points to a ashmem which will be used + * to fetch data during the submitFromAshmem. * - * @return {@code true} if the call succeeds. {@code false} if there is any native runtime - * error. + * @param ashmem fd that points to a ashmem + * @param size size of the ashmem file */ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) - public boolean write(byte[] buf) { + public boolean setAshmem(ParcelFileDescriptor ashmem, long size) { try { - return mService.write(buf); + return mService.setAshmem(ashmem, size); } catch (RemoteException e) { throw new RuntimeException(e.toString()); } } /** + * Submit bytes to the DSU partition from the ashmem previously set with + * setAshmem. + * + * @param size Number of bytes + * @return true on success, false otherwise. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) + public boolean submitFromAshmem(int size) { + try { + return mService.submitFromAshmem(size); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + /** * Finish write and make device to boot into the it after reboot. * * @return {@code true} if the call succeeds. {@code false} if there is any native runtime diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl index 2f4ab2d2420d..a6de170b5ce5 100644 --- a/core/java/android/os/image/IDynamicSystemService.aidl +++ b/core/java/android/os/image/IDynamicSystemService.aidl @@ -79,10 +79,20 @@ interface IDynamicSystemService boolean setEnable(boolean enable, boolean oneShot); /** - * Write a chunk of the DynamicSystem system image + * Set the file descriptor that points to a ashmem which will be used + * to fetch data during the submitFromAshmem. * - * @return true if the call succeeds + * @param fd fd that points to a ashmem + * @param size size of the ashmem file */ - boolean write(in byte[] buf); + boolean setAshmem(in ParcelFileDescriptor fd, long size); + /** + * Submit bytes to the DSU partition from the ashmem previously set with + * setAshmem. + * + * @param bytes number of bytes that can be read from stream. + * @return true on success, false otherwise. + */ + boolean submitFromAshmem(long bytes); } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 2299aad6f79a..a959913f6fb0 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -3592,10 +3592,23 @@ public final class MediaStore { } /** @hide */ + public static Uri scanFile(ContentProviderClient client, File file) { + return scan(client, SCAN_FILE_CALL, file, false); + } + + /** @hide */ private static Uri scan(Context context, String method, File file, boolean originatedFromShell) { final ContentResolver resolver = context.getContentResolver(); try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { + return scan(client, method, file, originatedFromShell); + } + } + + /** @hide */ + private static Uri scan(ContentProviderClient client, String method, File file, + boolean originatedFromShell) { + try { final Bundle in = new Bundle(); in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file)); in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9abb64f15e99..e48623572e95 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16,16 +16,19 @@ package android.provider; -import static android.provider.SettingsValidators.ANY_INTEGER_VALIDATOR; -import static android.provider.SettingsValidators.ANY_STRING_VALIDATOR; -import static android.provider.SettingsValidators.BOOLEAN_VALIDATOR; -import static android.provider.SettingsValidators.COMPONENT_NAME_VALIDATOR; -import static android.provider.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR; -import static android.provider.SettingsValidators.LOCALE_VALIDATOR; -import static android.provider.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR; -import static android.provider.SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR; -import static android.provider.SettingsValidators.PACKAGE_NAME_VALIDATOR; -import static android.provider.SettingsValidators.URI_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.TILE_LIST_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.URI_VALIDATOR; import android.Manifest; import android.annotation.IntDef; @@ -80,7 +83,12 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.UserHandle; -import android.provider.SettingsValidators.Validator; +import android.provider.settings.validators.ComponentNameListValidator; +import android.provider.settings.validators.DiscreteValueValidator; +import android.provider.settings.validators.InclusiveFloatRangeValidator; +import android.provider.settings.validators.InclusiveIntegerRangeValidator; +import android.provider.settings.validators.PackageNameListValidator; +import android.provider.settings.validators.Validator; import android.speech.tts.TextToSpeech; import android.telephony.SubscriptionManager; import android.text.TextUtils; @@ -3149,7 +3157,7 @@ public final class Settings { public static final String END_BUTTON_BEHAVIOR = "end_button_behavior"; private static final Validator END_BUTTON_BEHAVIOR_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 3); + new InclusiveIntegerRangeValidator(0, 3); /** * END_BUTTON_BEHAVIOR value for "go home". @@ -3351,7 +3359,7 @@ public final class Settings { "bluetooth_discoverability"; private static final Validator BLUETOOTH_DISCOVERABILITY_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 2); + new InclusiveIntegerRangeValidator(0, 2); /** * Bluetooth discoverability timeout. If this value is nonzero, then @@ -3495,7 +3503,7 @@ public final class Settings { public static final String PEAK_REFRESH_RATE = "peak_refresh_rate"; private static final Validator PEAK_REFRESH_RATE_VALIDATOR = - new SettingsValidators.InclusiveFloatRangeValidator(24f, Float.MAX_VALUE); + new InclusiveFloatRangeValidator(24f, Float.MAX_VALUE); /** * The amount of time in milliseconds before the device goes to sleep or begins @@ -3524,7 +3532,7 @@ public final class Settings { public static final String SCREEN_BRIGHTNESS_FOR_VR = "screen_brightness_for_vr"; private static final Validator SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 255); + new InclusiveIntegerRangeValidator(0, 255); /** * Control whether to enable automatic brightness mode. @@ -3542,7 +3550,7 @@ public final class Settings { public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj"; private static final Validator SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR = - new SettingsValidators.InclusiveFloatRangeValidator(-1, 1); + new InclusiveFloatRangeValidator(-1, 1); /** * SCREEN_BRIGHTNESS_MODE value for manual mode. @@ -3676,7 +3684,7 @@ public final class Settings { "haptic_feedback_intensity"; private static final Validator VIBRATION_INTENSITY_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 3); + new InclusiveIntegerRangeValidator(0, 3); /** * Ringer volume. This is used internally, changing this value will not @@ -3766,7 +3774,7 @@ public final class Settings { public static final String MASTER_BALANCE = "master_balance"; private static final Validator MASTER_BALANCE_VALIDATOR = - new SettingsValidators.InclusiveFloatRangeValidator(-1.f, 1.f); + new InclusiveFloatRangeValidator(-1.f, 1.f); /** * Whether the notifications should use the ring volume (value of 1) or @@ -4004,7 +4012,7 @@ public final class Settings { /** @hide */ public static final Validator TIME_12_24_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"12", "24", null}); + new DiscreteValueValidator(new String[] {"12", "24", null}); /** * Date format string @@ -4090,7 +4098,7 @@ public final class Settings { /** @hide */ public static final Validator USER_ROTATION_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 3); + new InclusiveIntegerRangeValidator(0, 3); /** * Control whether the rotation lock toggle in the System UI should be hidden. @@ -4179,7 +4187,7 @@ public final class Settings { /** @hide */ public static final Validator TTY_MODE_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 3); + new InclusiveIntegerRangeValidator(0, 3); /** * Whether the sounds effects (key clicks, lid open ...) are enabled. The value is @@ -4381,7 +4389,7 @@ public final class Settings { /** @hide */ public static final Validator SIP_CALL_OPTIONS_VALIDATOR = - new SettingsValidators.DiscreteValueValidator( + new DiscreteValueValidator( new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"}); /** @@ -4428,7 +4436,7 @@ public final class Settings { /** @hide */ public static final Validator POINTER_SPEED_VALIDATOR = - new SettingsValidators.InclusiveFloatRangeValidator(-7, 7); + new InclusiveFloatRangeValidator(-7, 7); /** * Whether lock-to-app will be triggered by long-press on recents. @@ -6352,7 +6360,7 @@ public final class Settings { public static final String LOCK_SCREEN_CUSTOM_CLOCK_FACE = "lock_screen_custom_clock_face"; private static final Validator LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR = - SettingsValidators.JSON_OBJECT_VALIDATOR; + JSON_OBJECT_VALIDATOR; /** * Indicates which clock face to show on lock screen and AOD while docked. @@ -6509,7 +6517,7 @@ public final class Settings { "enabled_accessibility_services"; private static final Validator ENABLED_ACCESSIBILITY_SERVICES_VALIDATOR = - new SettingsValidators.ComponentNameListValidator(":"); + new ComponentNameListValidator(":"); /** * List of the accessibility services to which the user has granted @@ -6521,7 +6529,7 @@ public final class Settings { "touch_exploration_granted_accessibility_services"; private static final Validator TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES_VALIDATOR = - new SettingsValidators.ComponentNameListValidator(":"); + new ComponentNameListValidator(":"); /** * Whether the Global Actions Panel is enabled. @@ -6696,7 +6704,7 @@ public final class Settings { "accessibility_display_magnification_scale"; private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE_VALIDATOR = - new SettingsValidators.InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE); + new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE); /** * Unused mangnification setting @@ -6780,7 +6788,7 @@ public final class Settings { "accessibility_captioning_preset"; private static final Validator ACCESSIBILITY_CAPTIONING_PRESET_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[]{"-1", "0", "1", "2", + new DiscreteValueValidator(new String[]{"-1", "0", "1", "2", "3", "4"}); /** @@ -6824,7 +6832,7 @@ public final class Settings { "accessibility_captioning_edge_type"; private static final Validator ACCESSIBILITY_CAPTIONING_EDGE_TYPE_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1", "2"}); + new DiscreteValueValidator(new String[]{"0", "1", "2"}); /** * Integer property that specifes the edge color for captions as a @@ -6870,7 +6878,7 @@ public final class Settings { "accessibility_captioning_typeface"; private static final Validator ACCESSIBILITY_CAPTIONING_TYPEFACE_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[]{"DEFAULT", + new DiscreteValueValidator(new String[]{"DEFAULT", "MONOSPACE", "SANS_SERIF", "SERIF"}); /** @@ -6882,7 +6890,7 @@ public final class Settings { "accessibility_captioning_font_scale"; private static final Validator ACCESSIBILITY_CAPTIONING_FONT_SCALE_VALIDATOR = - new SettingsValidators.InclusiveFloatRangeValidator(0.5f, 2.0f); + new InclusiveFloatRangeValidator(0.5f, 2.0f); /** * Setting that specifies whether display color inversion is enabled. @@ -6923,7 +6931,7 @@ public final class Settings { "accessibility_display_daltonizer"; private static final Validator ACCESSIBILITY_DISPLAY_DALTONIZER_VALIDATOR = - new SettingsValidators.DiscreteValueValidator( + new DiscreteValueValidator( new String[] {"-1", "0", "11", "12", "13"}); /** @@ -7112,8 +7120,7 @@ public final class Settings { */ public static final String TTS_DEFAULT_LOCALE = "tts_default_locale"; - private static final Validator TTS_DEFAULT_LOCALE_VALIDATOR = - new SettingsValidators.TTSListValidator(); + private static final Validator TTS_DEFAULT_LOCALE_VALIDATOR = TTS_LIST_VALIDATOR; /** * Space delimited list of plugin packages that are enabled. @@ -7121,7 +7128,7 @@ public final class Settings { public static final String TTS_ENABLED_PLUGINS = "tts_enabled_plugins"; private static final Validator TTS_ENABLED_PLUGINS_VALIDATOR = - new SettingsValidators.PackageNameListValidator(" "); + new PackageNameListValidator(" "); /** * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} @@ -7315,7 +7322,7 @@ public final class Settings { "preferred_tty_mode"; private static final Validator PREFERRED_TTY_MODE_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1", "2", "3"}); + new DiscreteValueValidator(new String[]{"0", "1", "2", "3"}); /** * Whether the enhanced voice privacy mode is enabled. @@ -7630,7 +7637,7 @@ public final class Settings { public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior"; private static final Validator INCALL_POWER_BUTTON_BEHAVIOR_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[]{"1", "2"}); + new DiscreteValueValidator(new String[]{"1", "2"}); /** * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen". @@ -7851,7 +7858,7 @@ public final class Settings { public static final String UI_NIGHT_MODE = "ui_night_mode"; private static final Validator UI_NIGHT_MODE_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 2); + new InclusiveIntegerRangeValidator(0, 2); /** * Whether screensavers are enabled. @@ -7871,7 +7878,7 @@ public final class Settings { public static final String SCREENSAVER_COMPONENTS = "screensaver_components"; private static final Validator SCREENSAVER_COMPONENTS_VALIDATOR = - new SettingsValidators.ComponentNameListValidator(","); + new ComponentNameListValidator(","); /** * If screensavers are enabled, whether the screensaver should be automatically launched @@ -8023,7 +8030,7 @@ public final class Settings { "enabled_notification_assistant"; private static final Validator ENABLED_NOTIFICATION_ASSISTANT_VALIDATOR = - new SettingsValidators.ComponentNameListValidator(":"); + new ComponentNameListValidator(":"); /** * Read only list of the service components that the current user has explicitly allowed to @@ -8038,7 +8045,7 @@ public final class Settings { public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners"; private static final Validator ENABLED_NOTIFICATION_LISTENERS_VALIDATOR = - new SettingsValidators.ComponentNameListValidator(":"); + new ComponentNameListValidator(":"); /** * Read only list of the packages that the current user has explicitly allowed to @@ -8053,7 +8060,7 @@ public final class Settings { "enabled_notification_policy_access_packages"; private static final Validator ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES_VALIDATOR = - new SettingsValidators.PackageNameListValidator(":"); + new PackageNameListValidator(":"); /** * Defines whether managed profile ringtones should be synced from it's parent profile @@ -8350,16 +8357,6 @@ public final class Settings { BOOLEAN_VALIDATOR; /** - * Whether or not the face unlock education screen has been shown to the user. - * @hide - */ - public static final String FACE_UNLOCK_EDUCATION_INFO_DISPLAYED = - "face_unlock_education_info_displayed"; - - private static final Validator FACE_UNLOCK_EDUCATION_INFO_DISPLAYED_VALIDATOR = - BOOLEAN_VALIDATOR; - - /** * Whether or not debugging is enabled. * @hide */ @@ -8446,7 +8443,7 @@ public final class Settings { public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode"; private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 2); + new InclusiveIntegerRangeValidator(0, 2); /** * Control the color temperature of Night Display, represented in Kelvin. @@ -8507,7 +8504,7 @@ public final class Settings { public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners"; private static final Validator ENABLED_VR_LISTENERS_VALIDATOR = - new SettingsValidators.ComponentNameListValidator(":"); + new ComponentNameListValidator(":"); /** * Behavior of the display while in VR mode. @@ -8519,7 +8516,7 @@ public final class Settings { public static final String VR_DISPLAY_MODE = "vr_display_mode"; private static final Validator VR_DISPLAY_MODE_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[]{"0", "1"}); + new DiscreteValueValidator(new String[]{"0", "1"}); /** * Lower the display persistence while the system is in VR mode. @@ -8638,8 +8635,7 @@ public final class Settings { */ public static final String QS_TILES = "sysui_qs_tiles"; - private static final Validator QS_TILES_VALIDATOR = - new SettingsValidators.TileListValidator(); + private static final Validator QS_TILES_VALIDATOR = TILE_LIST_VALIDATOR; /** * Specifies whether the web action API is enabled. @@ -8705,8 +8701,7 @@ public final class Settings { */ public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles"; - private static final Validator QS_AUTO_ADDED_TILES_VALIDATOR = - new SettingsValidators.TileListValidator(); + private static final Validator QS_AUTO_ADDED_TILES_VALIDATOR = TILE_LIST_VALIDATOR; /** * Whether the Lockdown button should be shown in the power menu. @@ -8867,7 +8862,7 @@ public final class Settings { "theme_customization_overlay_packages"; private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR = - SettingsValidators.JSON_OBJECT_VALIDATOR; + JSON_OBJECT_VALIDATOR; /** * Navigation bar mode. @@ -8879,7 +8874,7 @@ public final class Settings { public static final String NAVIGATION_MODE = "navigation_mode"; private static final Validator NAVIGATION_MODE_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2"}); + new DiscreteValueValidator(new String[] {"0", "1", "2"}); /** * Controls whether aware is enabled. @@ -9182,8 +9177,6 @@ public final class Settings { VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR); VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR); - VALIDATORS.put(FACE_UNLOCK_EDUCATION_INFO_DISPLAYED, - FACE_UNLOCK_EDUCATION_INFO_DISPLAYED_VALIDATOR); VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR); VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR); @@ -10583,22 +10576,6 @@ public final class Settings { BOOLEAN_VALIDATOR; /** - * Whether to notify the user of carrier networks. - * <p> - * If not connected and the scan results have a carrier network, we will - * put this notification up. If we attempt to connect to a network or - * the carrier network(s) disappear, we remove the notification. When we - * show the notification, we will not show it again for - * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time. - * @hide - */ - public static final String WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON = - "wifi_carrier_networks_available_notification_on"; - - private static final Validator WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR = - BOOLEAN_VALIDATOR; - - /** * {@hide} */ public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON = @@ -10733,7 +10710,7 @@ public final class Settings { "network_recommendations_enabled"; private static final Validator NETWORK_RECOMMENDATIONS_ENABLED_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"-1", "0", "1"}); + new DiscreteValueValidator(new String[] {"-1", "0", "1"}); /** * Which package name to use for network recommendations. If null, network recommendations @@ -11058,12 +11035,6 @@ public final class Settings { public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name"; /** - * The min time between wifi disable and wifi enable - * @hide - */ - public static final String WIFI_REENABLE_DELAY_MS = "wifi_reenable_delay"; - - /** * Timeout for ephemeral networks when all known BSSIDs go out of range. We will disconnect * from an ephemeral network if there is no BSSID for that network with a non-null score that * has been seen in this time period. @@ -12590,7 +12561,7 @@ public final class Settings { public static final String EMERGENCY_TONE = "emergency_tone"; private static final Validator EMERGENCY_TONE_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2"}); + new DiscreteValueValidator(new String[] {"0", "1", "2"}); /** * CDMA only settings @@ -12620,7 +12591,7 @@ public final class Settings { "enable_automatic_system_server_heap_dumps"; private static final Validator ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"}); + new DiscreteValueValidator(new String[] {"0", "1"}); /** * See RIL_PreferredNetworkType in ril.h @@ -12814,7 +12785,7 @@ public final class Settings { "low_power_sticky_auto_disable_level"; private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 100); + new InclusiveIntegerRangeValidator(0, 100); /** * Whether sticky battery saver should be deactivated once the battery level has reached the @@ -12826,7 +12797,7 @@ public final class Settings { "low_power_sticky_auto_disable_enabled"; private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"}); + new DiscreteValueValidator(new String[] {"0", "1"}); /** * Battery level [1-100] at which low power mode automatically turns on. @@ -12841,7 +12812,7 @@ public final class Settings { public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level"; private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 100); + new InclusiveIntegerRangeValidator(0, 100); /** * Whether battery saver is currently set to trigger based on percentage, dynamic power @@ -12854,7 +12825,7 @@ public final class Settings { public static final String AUTOMATIC_POWER_SAVE_MODE = "automatic_power_save_mode"; private static final Validator AUTOMATIC_POWER_SAVE_MODE_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"}); + new DiscreteValueValidator(new String[] {"0", "1"}); /** * The setting that backs the disable threshold for the setPowerSavingsWarning api in @@ -12867,7 +12838,7 @@ public final class Settings { public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold"; private static final Validator DYNAMIC_POWER_SAVINGS_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 100); + new InclusiveIntegerRangeValidator(0, 100); /** * The setting which backs the setDynamicPowerSaveHint api in PowerManager. @@ -13017,7 +12988,7 @@ public final class Settings { public static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output"; private static final Validator ENCODED_SURROUND_OUTPUT_VALIDATOR = - new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1", "2", "3"}); + new DiscreteValueValidator(new String[] {"0", "1", "2", "3"}); /** * Surround sounds formats that are enabled when ENCODED_SURROUND_OUTPUT is set to @@ -13768,7 +13739,7 @@ public final class Settings { public static final String POWER_BUTTON_LONG_PRESS = "power_button_long_press"; private static final Validator POWER_BUTTON_LONG_PRESS_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 5); + new InclusiveIntegerRangeValidator(0, 5); /** * Overrides internal R.integer.config_veryLongPressOnPowerBehavior. @@ -13779,7 +13750,7 @@ public final class Settings { public static final String POWER_BUTTON_VERY_LONG_PRESS = "power_button_very_long_press"; private static final Validator POWER_BUTTON_VERY_LONG_PRESS_VALIDATOR = - new SettingsValidators.InclusiveIntegerRangeValidator(0, 1); + new InclusiveIntegerRangeValidator(0, 1); /** * Settings to backup. This is here so that it's in the same place as the settings @@ -13814,7 +13785,6 @@ public final class Settings { NETWORK_RECOMMENDATIONS_ENABLED, WIFI_WAKEUP_ENABLED, WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, - WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON, USE_OPEN_WIFI_PACKAGE, WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, EMERGENCY_TONE, @@ -13883,8 +13853,6 @@ public final class Settings { VALIDATORS.put(PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_VALIDATOR); VALIDATORS.put(PRIVATE_DNS_SPECIFIER, PRIVATE_DNS_SPECIFIER_VALIDATOR); VALIDATORS.put(SOFT_AP_TIMEOUT_ENABLED, SOFT_AP_TIMEOUT_ENABLED_VALIDATOR); - VALIDATORS.put(WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON, - WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR); VALIDATORS.put(WIFI_SCAN_THROTTLE_ENABLED, WIFI_SCAN_THROTTLE_ENABLED_VALIDATOR); VALIDATORS.put(APP_AUTO_RESTRICTION_ENABLED, APP_AUTO_RESTRICTION_ENABLED_VALIDATOR); VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR); diff --git a/core/java/android/provider/settings/validators/ComponentNameListValidator.java b/core/java/android/provider/settings/validators/ComponentNameListValidator.java new file mode 100644 index 000000000000..b6b867a5d513 --- /dev/null +++ b/core/java/android/provider/settings/validators/ComponentNameListValidator.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR; + +/** + * Validate a list of compoments. + * + * @hide + */ +public final class ComponentNameListValidator extends ListValidator { + public ComponentNameListValidator(String separator) { + super(separator); + } + + @Override + protected boolean isEntryValid(String entry) { + return entry != null; + } + + @Override + protected boolean isItemValid(String item) { + return COMPONENT_NAME_VALIDATOR.validate(item); + } +} diff --git a/core/java/android/provider/settings/validators/DiscreteValueValidator.java b/core/java/android/provider/settings/validators/DiscreteValueValidator.java new file mode 100644 index 000000000000..183651f77f5a --- /dev/null +++ b/core/java/android/provider/settings/validators/DiscreteValueValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import android.annotation.Nullable; + +import com.android.internal.util.ArrayUtils; + +/** + * Validate a value exists in an array of known good values + * + * @hide + */ +public final class DiscreteValueValidator implements Validator { + private final String[] mValues; + + public DiscreteValueValidator(String[] values) { + mValues = values; + } + + @Override + public boolean validate(@Nullable String value) { + return ArrayUtils.contains(mValues, value); + } +} diff --git a/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java b/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java new file mode 100644 index 000000000000..38400ac676a8 --- /dev/null +++ b/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import android.annotation.Nullable; + +/** + * Validate a float value lies within a given (boundary inclusive) range. + * + * @hide + */ +public final class InclusiveFloatRangeValidator implements Validator { + private final float mMin; + private final float mMax; + + public InclusiveFloatRangeValidator(float min, float max) { + mMin = min; + mMax = max; + } + + @Override + public boolean validate(@Nullable String value) { + try { + final float floatValue = Float.parseFloat(value); + return floatValue >= mMin && floatValue <= mMax; + } catch (NumberFormatException | NullPointerException e) { + return false; + } + } +} diff --git a/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java b/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java new file mode 100644 index 000000000000..e53c252c7d35 --- /dev/null +++ b/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import android.annotation.Nullable; + +/** + * Validate an integer value lies within a given (boundary inclusive) range. + * + * @hide + */ +public final class InclusiveIntegerRangeValidator implements Validator { + private final int mMin; + private final int mMax; + + public InclusiveIntegerRangeValidator(int min, int max) { + mMin = min; + mMax = max; + } + + @Override + public boolean validate(@Nullable String value) { + try { + final int intValue = Integer.parseInt(value); + return intValue >= mMin && intValue <= mMax; + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/core/java/android/provider/settings/validators/ListValidator.java b/core/java/android/provider/settings/validators/ListValidator.java new file mode 100644 index 000000000000..a6001d2e10c1 --- /dev/null +++ b/core/java/android/provider/settings/validators/ListValidator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import android.annotation.Nullable; + +/** + * Validate the elements in a list. + * + * @hide + */ +abstract class ListValidator implements Validator { + + private String mListSplitRegex; + + ListValidator(String listSplitRegex) { + mListSplitRegex = listSplitRegex; + } + + public boolean validate(@Nullable String value) { + if (!isEntryValid(value)) { + return false; + } + String[] items = value.split(","); + for (String item : items) { + if (!isItemValid(item)) { + return false; + } + } + return true; + } + + protected abstract boolean isEntryValid(String entry); + + protected abstract boolean isItemValid(String item); +} + diff --git a/core/java/android/provider/settings/validators/PackageNameListValidator.java b/core/java/android/provider/settings/validators/PackageNameListValidator.java new file mode 100644 index 000000000000..bc7fc13bdc6d --- /dev/null +++ b/core/java/android/provider/settings/validators/PackageNameListValidator.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR; + +/** + * Validate a list of package names. + * + * @hide + */ +public final class PackageNameListValidator extends ListValidator { + public PackageNameListValidator(String separator) { + super(separator); + } + + @Override + protected boolean isEntryValid(String entry) { + return entry != null; + } + + @Override + protected boolean isItemValid(String item) { + return PACKAGE_NAME_VALIDATOR.validate(item); + } +} diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/settings/validators/SettingsValidators.java index e4cf9fd6aaee..562c638d0b5a 100644 --- a/core/java/android/provider/SettingsValidators.java +++ b/core/java/android/provider/settings/validators/SettingsValidators.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 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. @@ -14,15 +14,13 @@ * limitations under the License. */ -package android.provider; +package android.provider.settings.validators; import android.annotation.Nullable; import android.content.ComponentName; import android.net.Uri; import android.text.TextUtils; -import com.android.internal.util.ArrayUtils; - import org.json.JSONException; import org.json.JSONObject; @@ -179,154 +177,7 @@ public class SettingsValidators { } }; - public interface Validator { - /** - * Returns whether the input value is valid. Subclasses should handle the case where the - * input value is {@code null}. - */ - boolean validate(@Nullable String value); - } - - public static final class DiscreteValueValidator implements Validator { - private final String[] mValues; - - public DiscreteValueValidator(String[] values) { - mValues = values; - } - - @Override - public boolean validate(@Nullable String value) { - return ArrayUtils.contains(mValues, value); - } - } - - public static final class InclusiveIntegerRangeValidator implements Validator { - private final int mMin; - private final int mMax; - - public InclusiveIntegerRangeValidator(int min, int max) { - mMin = min; - mMax = max; - } - - @Override - public boolean validate(@Nullable String value) { - try { - final int intValue = Integer.parseInt(value); - return intValue >= mMin && intValue <= mMax; - } catch (NumberFormatException e) { - return false; - } - } - } - - public static final class InclusiveFloatRangeValidator implements Validator { - private final float mMin; - private final float mMax; - - public InclusiveFloatRangeValidator(float min, float max) { - mMin = min; - mMax = max; - } - - @Override - public boolean validate(@Nullable String value) { - try { - final float floatValue = Float.parseFloat(value); - return floatValue >= mMin && floatValue <= mMax; - } catch (NumberFormatException | NullPointerException e) { - return false; - } - } - } - - public static final class ComponentNameListValidator implements Validator { - private final String mSeparator; - - public ComponentNameListValidator(String separator) { - mSeparator = separator; - } - - @Override - public boolean validate(@Nullable String value) { - if (value == null) { - return false; - } - String[] elements = value.split(mSeparator); - for (String element : elements) { - if (!COMPONENT_NAME_VALIDATOR.validate(element)) { - return false; - } - } - return true; - } - } - - public static final class PackageNameListValidator implements Validator { - private final String mSeparator; - - public PackageNameListValidator(String separator) { - mSeparator = separator; - } - - @Override - public boolean validate(@Nullable String value) { - if (value == null) { - return false; - } - String[] elements = value.split(mSeparator); - for (String element : elements) { - if (!PACKAGE_NAME_VALIDATOR.validate(element)) { - return false; - } - } - return true; - } - } - - private abstract static class ListValidator implements Validator { - public boolean validate(@Nullable String value) { - if (!isEntryValid(value)) { - return false; - } - String[] items = value.split(","); - for (String item : items) { - if (!isItemValid(item)) { - return false; - } - } - return true; - } - - protected abstract boolean isEntryValid(String entry); - - protected abstract boolean isItemValid(String item); - } + public static final Validator TTS_LIST_VALIDATOR = new TTSListValidator(); - /** Ensure a restored value is a string in the format the text-to-speech system handles */ - public static final class TTSListValidator extends ListValidator { - protected boolean isEntryValid(String entry) { - return entry != null && entry.length() > 0; - } - - protected boolean isItemValid(String item) { - String[] parts = item.split(":"); - // Replaces any old language separator (-) with the new one (_) - return ((parts.length == 2) - && (parts[0].length() > 0) - && ANY_STRING_VALIDATOR.validate(parts[0]) - && LOCALE_VALIDATOR.validate(parts[1].replace('-', '_'))); - } - } - - /** Ensure a restored value is suitable to be used as a tile name */ - public static final class TileListValidator extends ListValidator { - protected boolean isEntryValid(String entry) { - return entry != null; - } - - protected boolean isItemValid(String item) { - return item.length() > 0 && ANY_STRING_VALIDATOR.validate(item); - } - } + public static final Validator TILE_LIST_VALIDATOR = new TileListValidator(); } diff --git a/core/java/android/provider/settings/validators/TTSListValidator.java b/core/java/android/provider/settings/validators/TTSListValidator.java new file mode 100644 index 000000000000..6c73471a8e8e --- /dev/null +++ b/core/java/android/provider/settings/validators/TTSListValidator.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR; + +/** + * Ensure a restored value is a string in the format the text-to-speech system handles + * + * @hide + */ +final class TTSListValidator extends ListValidator { + + TTSListValidator() { + super(","); + } + + protected boolean isEntryValid(String entry) { + return entry != null && entry.length() > 0; + } + + protected boolean isItemValid(String item) { + String[] parts = item.split(":"); + // Replaces any old language separator (-) with the new one (_) + return ((parts.length == 2) + && (parts[0].length() > 0) + && ANY_STRING_VALIDATOR.validate(parts[0]) + && LOCALE_VALIDATOR.validate(parts[1].replace('-', '_'))); + } +} diff --git a/core/java/android/provider/settings/validators/TileListValidator.java b/core/java/android/provider/settings/validators/TileListValidator.java new file mode 100644 index 000000000000..c69644252bcd --- /dev/null +++ b/core/java/android/provider/settings/validators/TileListValidator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; + +/** + * Ensure a restored value is suitable to be used as a tile name + * + * @hide + */ +final class TileListValidator extends ListValidator { + TileListValidator() { + super(","); + } + + protected boolean isEntryValid(String entry) { + return entry != null; + } + + protected boolean isItemValid(String item) { + return item.length() > 0 && ANY_STRING_VALIDATOR.validate(item); + } +} diff --git a/core/java/android/provider/settings/validators/Validator.java b/core/java/android/provider/settings/validators/Validator.java new file mode 100644 index 000000000000..393a03ddf916 --- /dev/null +++ b/core/java/android/provider/settings/validators/Validator.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider.settings.validators; + +import android.annotation.Nullable; + +/** + * Interface for a settings value validator. + * + * @hide + */ +public interface Validator { + /** + * Returns whether the input value is valid. Subclasses should handle the case where the + * input value is {@code null}. + */ + boolean validate(@Nullable String value); +} diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java index 8b5659b3fa31..3a1df3ec861e 100644 --- a/core/java/android/util/LocalLog.java +++ b/core/java/android/util/LocalLog.java @@ -60,9 +60,19 @@ public final class LocalLog { } public synchronized void dump(PrintWriter pw) { + dump("", pw); + } + + /** + * Dumps the content of local log to print writer with each log entry predeced with indent + * + * @param indent indent that precedes each log entry + * @param pw printer writer to write into + */ + public synchronized void dump(String indent, PrintWriter pw) { Iterator<String> itr = mLog.iterator(); while (itr.hasNext()) { - pw.println(itr.next()); + pw.printf("%s%s\n", indent, itr.next()); } } diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 42275a3e3165..d7d7e210e5f4 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -856,6 +856,8 @@ public class AccessibilityRecord { mScrollY = record.mScrollY; mMaxScrollX = record.mMaxScrollX; mMaxScrollY = record.mMaxScrollY; + mScrollDeltaX = record.mScrollDeltaX; + mScrollDeltaY = record.mScrollDeltaY; mAddedCount = record.mAddedCount; mRemovedCount = record.mRemovedCount; mClassName = record.mClassName; @@ -882,6 +884,8 @@ public class AccessibilityRecord { mScrollY = 0; mMaxScrollX = 0; mMaxScrollY = 0; + mScrollDeltaX = UNDEFINED; + mScrollDeltaY = UNDEFINED; mAddedCount = UNDEFINED; mRemovedCount = UNDEFINED; mClassName = null; @@ -921,6 +925,8 @@ public class AccessibilityRecord { append(builder, "ScrollY", mScrollY); append(builder, "MaxScrollX", mMaxScrollX); append(builder, "MaxScrollY", mMaxScrollY); + append(builder, "ScrollDeltaX", mScrollDeltaX); + append(builder, "ScrollDeltaY", mScrollDeltaY); append(builder, "AddedCount", mAddedCount); append(builder, "RemovedCount", mRemovedCount); append(builder, "ParcelableData", mParcelableData); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index cee79439d1b3..1e7440bd5a43 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -480,6 +480,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { return; } + mNextFlushForTextChanged = false; + final int numberEvents = mEvents.size(); final String reasonString = getFlushReasonAsString(reason); if (sDebug) { @@ -495,10 +497,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession { try { mHandler.removeMessages(MSG_FLUSH); - if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) { - mNextFlushForTextChanged = false; - } - final ParceledListSlice<ContentCaptureEvent> events = clearEvents(); mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions); } catch (RemoteException e) { diff --git a/core/java/android/webkit/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java index 18ec334d0283..ac145b1d81a5 100644 --- a/core/java/android/webkit/PermissionRequest.java +++ b/core/java/android/webkit/PermissionRequest.java @@ -32,7 +32,7 @@ import android.net.Uri; * avoid unintentionally granting requests for new permissions, you should pass the * specific permissions you intend to grant to {@link #grant(String[]) grant()}, * and avoid writing code like this example: - * <pre> + * <pre class="prettyprint"> * permissionRequest.grant(permissionRequest.getResources()) // This is wrong!!! * </pre> * See the WebView's release notes for information about new protected resources. diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 95fe963d7891..4db630808ef1 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -519,15 +519,17 @@ public class WebChromeClient { * may not be supported and applications wishing to support these sources * or more advanced file operations should build their own Intent. * - * <pre> - * How to use: - * 1. Build an intent using {@link #createIntent} - * 2. Fire the intent using {@link android.app.Activity#startActivityForResult}. - * 3. Check for ActivityNotFoundException and take a user friendly action if thrown. - * 4. Listen the result using {@link android.app.Activity#onActivityResult} - * 5. Parse the result using {@link #parseResult} only if media capture was not requested. - * 6. Send the result using filePathCallback of {@link WebChromeClient#onShowFileChooser} - * </pre> + * <p>How to use: + * <ol> + * <li>Build an intent using {@link #createIntent}</li> + * <li>Fire the intent using {@link android.app.Activity#startActivityForResult}.</li> + * <li>Check for ActivityNotFoundException and take a user friendly action if thrown.</li> + * <li>Listen the result using {@link android.app.Activity#onActivityResult}</li> + * <li>Parse the result using {@link #parseResult} only if media capture was not + * requested.</li> + * <li>Send the result using filePathCallback of {@link + * WebChromeClient#onShowFileChooser}</li> + * </ol> * * @return an Intent that supports basic file chooser sources. */ diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index a35659e307d5..e4b5eaa56aa6 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -762,7 +762,7 @@ public class WebView extends AbsoluteLayout * encoded. If the data is base64 encoded, the value of the encoding * parameter must be {@code "base64"}. HTML can be encoded with {@link * android.util.Base64#encodeToString(byte[],int)} like so: - * <pre> + * <pre class="prettyprint"> * String unencodedHtml = * "<html><body>'%28' is the code for '('</body></html>"; * String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING); @@ -1854,7 +1854,7 @@ public class WebView extends AbsoluteLayout * important security note below for implications. * <p> Note that injected objects will not appear in JavaScript until the page is next * (re)loaded. JavaScript should be enabled before injecting the object. For example: - * <pre> + * <pre class="prettyprint"> * class JsObject { * {@literal @}JavascriptInterface * public String toString() { return "injectedObject"; } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 00206fc38d1d..faeecda7250c 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -164,6 +164,8 @@ public class ChooserActivity extends ResolverActivity { public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share"; private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20; public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter"; + + private boolean mIsAppPredictorComponentAvailable; private AppPredictor mAppPredictor; private AppPredictor.Callback mAppPredictorCallback; private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache; @@ -617,6 +619,9 @@ public class ChooserActivity extends ResolverActivity { .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType()) .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost)); + // This is the only place this value is being set. Effectively final. + mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable(); + AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(); if (appPredictor != null) { mDirectShareAppTargetCache = new HashMap<>(); @@ -707,6 +712,32 @@ public class ChooserActivity extends ResolverActivity { } /** + * Returns true if app prediction service is defined and the component exists on device. + */ + private boolean isAppPredictionServiceAvailable() { + final String appPredictionServiceName = + getString(R.string.config_defaultAppPredictionService); + if (appPredictionServiceName == null) { + return false; + } + final ComponentName appPredictionComponentName = + ComponentName.unflattenFromString(appPredictionServiceName); + if (appPredictionComponentName == null) { + return false; + } + + // Check if the app prediction component actually exists on the device. + Intent intent = new Intent(); + intent.setComponent(appPredictionComponentName); + if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) { + Log.e(TAG, "App prediction service is defined, but does not exist: " + + appPredictionServiceName); + return false; + } + return true; + } + + /** * Check if the profile currently used is a work profile. * @return true if it is work profile, false if it is parent profile (or no work profile is * set up) @@ -1693,6 +1724,9 @@ public class ChooserActivity extends ResolverActivity { @Nullable private AppPredictor getAppPredictor() { + if (!mIsAppPredictorComponentAvailable) { + return null; + } if (mAppPredictor == null && getPackageManager().getAppPredictionServicePackageName() != null) { final IntentFilter filter = getTargetIntentFilter(); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index cf0394d466ea..9441825a1ed6 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -153,7 +153,7 @@ oneway interface IStatusBar // Used to show the dialog when BiometricService starts authentication void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, - boolean requireConfirmation, int userId); + boolean requireConfirmation, int userId, String opPackageName); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(boolean authenticated, String failureReason); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 85ae18e86793..4c3a177a013b 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -101,7 +101,7 @@ interface IStatusBarService // Used to show the dialog when BiometricService starts authentication void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, - boolean requireConfirmation, int userId); + boolean requireConfirmation, int userId, String opPackageName); // Used to hide the dialog when a biometric is authenticated void onBiometricAuthenticated(boolean authenticated, String failureReason); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5cbf81c30a3f..096bf6977a9e 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -497,11 +497,8 @@ static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj, auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - std::vector<uint8_t> byteData(parcel->dataSize()); - memcpy(byteData.data(), parcel->data(), parcel->dataSize()); - SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); - transaction->setMetadata(ctrl, id, std::move(byteData)); + transaction->setMetadata(ctrl, id, *parcel); } static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj, diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 00706941a18f..f2ca0a4e5fad 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -995,7 +995,7 @@ message GlobalSettingsProto { optional SettingProto display_certification_on = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto display_wps_config = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto networks_available_notification_on = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; - optional SettingProto carrier_networks_available_notification_on = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; + reserved 7; reserved "carrier_networks_available_notification_on"; optional SettingProto networks_available_repeat_delay = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto country_code = 9; optional SettingProto framework_scan_interval_ms = 10 [ (android.privacy).dest = DEST_AUTOMATIC ]; @@ -1013,7 +1013,7 @@ message GlobalSettingsProto { optional SettingProto watchdog_poor_network_test_enabled = 22 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto suspend_optimizations_enabled = 23 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto verbose_logging_enabled = 24 [ (android.privacy).dest = DEST_AUTOMATIC ]; - reserved 25; // connected_mac_randomization_enabled + reserved 25; reserved "connected_mac_randomization_enabled"; optional SettingProto max_dhcp_retry_count = 26 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto mobile_data_transition_wakelock_timeout_ms = 27 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Controls whether WiFi configurations created by a Device Owner app should @@ -1025,7 +1025,7 @@ message GlobalSettingsProto { optional SettingProto device_owner_configs_lockdown = 28 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto frequency_band = 29 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto p2p_device_name = 30; - optional SettingProto reenable_delay_ms = 31 [ (android.privacy).dest = DEST_AUTOMATIC ]; + reserved 31; reserved "reenable_delay_ms"; optional SettingProto ephemeral_out_of_range_timeout_ms = 32 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto on_when_proxy_disconnected = 33 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto bounce_delay_override_ms = 34 [ (android.privacy).dest = DEST_AUTOMATIC ]; diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index aac144c4d732..79167ab476c1 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -308,6 +308,8 @@ message ConstantsProto { // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't // ready now. reserved 1; // skip_not_ready_jobs + // Whether or not TimeController will use a non-wakeup alarm for delay constraints. + optional bool use_non_wakeup_alarm_for_delay = 2; } optional TimeController time_controller = 25; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 40c2cbedc73d..ceccd0dcdfec 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4286,4 +4286,9 @@ <!-- The list of packages to automatically opt out of refresh rates higher than 60hz because of known compatibility issues. --> <string-array name="config_highRefreshRateBlacklist"></string-array> + + <!-- Whether or not to hide the navigation bar when the soft keyboard is visible in order to + create additional screen real estate outside beyond the keyboard. Note that the user needs + to have a confirmed way to dismiss the keyboard when desired. --> + <bool name="config_automotiveHideNavBarForKeyboard">false</bool> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d1d7bf5100d6..cdfa0439c7ca 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3377,8 +3377,6 @@ <!-- Notification title for a nearby open wireless network.--> <string name="wifi_available_title">Connect to open Wi\u2011Fi network</string> - <!-- Notification title for a nearby carrier wireless network.--> - <string name="wifi_available_carrier_network_title">Connect to carrier Wi\u2011Fi network</string> <!-- Notification title when the system is connecting to the specified network. The network name is specified in the notification content. --> <string name="wifi_available_title_connecting">Connecting to Wi\u2011Fi network</string> <!-- Notification title when the system has connected to the network. The network name is specified in the notification content. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f7ae453599bb..c37a457ad12e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2070,7 +2070,6 @@ <java-symbol type="plurals" name="wifi_available" /> <java-symbol type="plurals" name="wifi_available_detailed" /> <java-symbol type="string" name="wifi_available_title" /> - <java-symbol type="string" name="wifi_available_carrier_network_title" /> <java-symbol type="string" name="wifi_available_title_connecting" /> <java-symbol type="string" name="wifi_available_title_connected" /> <java-symbol type="string" name="wifi_available_title_failed_to_connect" /> @@ -3847,4 +3846,6 @@ <java-symbol type="string" name="config_factoryResetPackage" /> <java-symbol type="array" name="config_highRefreshRateBlacklist" /> + <java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" /> + </resources> diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java index 3d9a1d9398c5..7c6271cbdf61 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,6 +44,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationWithTimeout; import java.util.Arrays; import java.util.HashSet; @@ -56,6 +58,8 @@ import java.util.List; public class StartProgramListUpdatesFanoutTest { private static final String TAG = "BroadcastRadioTests.hal2.StartProgramListUpdatesFanout"; + private static final VerificationWithTimeout CB_TIMEOUT = timeout(100); + // Mocks @Mock IBroadcastRadio mBroadcastRadioMock; @Mock ITunerSession mHalTunerSessionMock; @@ -200,10 +204,10 @@ public class StartProgramListUpdatesFanoutTest { // Adding mDabEnsembleInfo should not update any client. updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null); - verify(mAidlTunerCallbackMocks[0], times(1)).onProgramListUpdated(any()); - verify(mAidlTunerCallbackMocks[1], times(2)).onProgramListUpdated(any()); - verify(mAidlTunerCallbackMocks[2], times(1)).onProgramListUpdated(any()); - verify(mAidlTunerCallbackMocks[3], times(2)).onProgramListUpdated(any()); + verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); + verify(mAidlTunerCallbackMocks[1], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); + verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); + verify(mAidlTunerCallbackMocks[3], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); } @Test @@ -240,7 +244,7 @@ public class StartProgramListUpdatesFanoutTest { // Update the HAL with mModifiedAmFmInfo, and verify only the remaining client is updated. updateHalProgramInfo(true, Arrays.asList(mModifiedAmFmInfo), null); - verify(mAidlTunerCallbackMocks[0], times(1)).onProgramListUpdated(any()); + verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, Arrays.asList(mModifiedAmFmInfo), null); @@ -313,6 +317,6 @@ public class StartProgramListUpdatesFanoutTest { } ProgramList.Chunk expectedChunk = new ProgramList.Chunk(purge, true, modifiedSet, removedSet); - verify(clientMock).onProgramListUpdated(expectedChunk); + verify(clientMock, CB_TIMEOUT).onProgramListUpdated(expectedChunk); } } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index bdbf36881741..97b7ae964f33 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -544,7 +544,6 @@ public class SettingsBackupTest { Settings.Global.WIFI_ON, Settings.Global.WIFI_P2P_DEVICE_NAME, Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, - Settings.Global.WIFI_REENABLE_DELAY_MS, Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS, Settings.Global.WIFI_SAVED_STATE, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, @@ -721,7 +720,6 @@ public class SettingsBackupTest { Settings.Secure.BIOMETRIC_DEBUG_ENABLED, Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED, - Settings.Secure.FACE_UNLOCK_EDUCATION_INFO_DISPLAYED, Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED); @Test diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java index eea8c83e036b..5f042d3dce0f 100644 --- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java +++ b/core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.provider; +package android.provider.settings.validators; import static com.google.common.truth.Truth.assertThat; @@ -23,7 +23,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.platform.test.annotations.Presubmit; -import android.provider.SettingsValidators.Validator; +import android.provider.Settings; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -138,7 +138,7 @@ public class SettingsValidatorsTest { @Test public void testDiscreteValueValidator() { String[] beerTypes = new String[]{"Ale", "American IPA", "Stout"}; - Validator v = new SettingsValidators.DiscreteValueValidator(beerTypes); + Validator v = new DiscreteValueValidator(beerTypes); assertTrue(v.validate("Ale")); assertTrue(v.validate("American IPA")); assertTrue(v.validate("Stout")); @@ -148,14 +148,14 @@ public class SettingsValidatorsTest { @Test public void testDiscreteValueValidator_onNullValue_returnsFalse() { String[] discreteTypes = new String[]{"Type1", "Type2"}; - Validator v = new SettingsValidators.DiscreteValueValidator(discreteTypes); + Validator v = new DiscreteValueValidator(discreteTypes); assertFalse(v.validate(null)); } @Test public void testInclusiveIntegerRangeValidator() { - Validator v = new SettingsValidators.InclusiveIntegerRangeValidator(0, 5); + Validator v = new InclusiveIntegerRangeValidator(0, 5); assertTrue(v.validate("0")); assertTrue(v.validate("2")); assertTrue(v.validate("5")); @@ -165,14 +165,14 @@ public class SettingsValidatorsTest { @Test public void testInclusiveIntegerRangeValidator_onNullValue_returnsFalse() { - Validator v = new SettingsValidators.InclusiveIntegerRangeValidator(0, 5); + Validator v = new InclusiveIntegerRangeValidator(0, 5); assertFalse(v.validate(null)); } @Test public void testInclusiveFloatRangeValidator() { - Validator v = new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 5.0f); + Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f); assertTrue(v.validate("0.0")); assertTrue(v.validate("2.0")); assertTrue(v.validate("5.0")); @@ -182,14 +182,14 @@ public class SettingsValidatorsTest { @Test public void testInclusiveFloatRangeValidator_onNullValue_returnsFalse() { - Validator v = new SettingsValidators.InclusiveFloatRangeValidator(0.0f, 5.0f); + Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f); assertFalse(v.validate(null)); } @Test public void testComponentNameListValidator() { - Validator v = new SettingsValidators.ComponentNameListValidator(","); + Validator v = new ComponentNameListValidator(","); assertTrue(v.validate("com.android.localtransport/.LocalTransport," + "com.google.android.gms/.backup.migrate.service.D2dTransport")); assertFalse(v.validate("com.google.5android,android")); @@ -197,21 +197,21 @@ public class SettingsValidatorsTest { @Test public void testComponentNameListValidator_onNullValue_returnsFalse() { - Validator v = new SettingsValidators.ComponentNameListValidator(","); + Validator v = new ComponentNameListValidator(","); assertFalse(v.validate(null)); } @Test public void testPackageNameListValidator() { - Validator v = new SettingsValidators.PackageNameListValidator(","); + Validator v = new PackageNameListValidator(","); assertTrue(v.validate("com.android.localtransport.LocalTransport,com.google.android.gms")); assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android")); } @Test public void testPackageNameListValidator_onNullValue_returnsFalse() { - Validator v = new SettingsValidators.PackageNameListValidator(","); + Validator v = new PackageNameListValidator(","); assertFalse(v.validate(null)); } @@ -256,51 +256,41 @@ public class SettingsValidatorsTest { @Test public void testTTSListValidator_withValidInput_returnsTrue() { - Validator v = new SettingsValidators.TTSListValidator(); - - assertTrue(v.validate("com.foo.ttsengine:en-US,com.bar.ttsengine:es_ES")); + assertTrue( + SettingsValidators.TTS_LIST_VALIDATOR.validate( + "com.foo.ttsengine:en-US,com.bar.ttsengine:es_ES")); } @Test public void testTTSListValidator_withInvalidInput_returnsFalse() { - Validator v = new SettingsValidators.TTSListValidator(); - - assertFalse(v.validate("com.foo.ttsengine:eng-USA,INVALID")); + assertFalse( + SettingsValidators.TTS_LIST_VALIDATOR.validate( + "com.foo.ttsengine:eng-USA,INVALID")); } @Test public void testTTSListValidator_withEmptyInput_returnsFalse() { - Validator v = new SettingsValidators.TTSListValidator(); - - assertFalse(v.validate("")); + assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate("")); } @Test public void testTTSListValidator_withNullInput_returnsFalse() { - Validator v = new SettingsValidators.TTSListValidator(); - - assertFalse(v.validate(null)); + assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate(null)); } @Test public void testTileListValidator_withValidInput_returnsTrue() { - Validator v = new SettingsValidators.TileListValidator(); - - assertTrue(v.validate("1,2,3,4")); + assertTrue(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,2,3,4")); } @Test public void testTileListValidator_withMissingValue_returnsFalse() { - Validator v = new SettingsValidators.TileListValidator(); - - assertFalse(v.validate("1,,3")); + assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,,3")); } @Test public void testTileListValidator_withNullInput_returnsFalse() { - Validator v = new SettingsValidators.TileListValidator(); - - assertFalse(v.validate(null)); + assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate(null)); } @Test diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 89d3cc4f5083..16f2917f8df8 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -20,6 +20,7 @@ cc_library_shared { ], shared_libs: [ + "libbinder", "libcutils", "liblog", "libutils", diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index c1868d3a94d6..fd386e9f7a8a 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -245,7 +245,8 @@ void SpriteController::doUpdateSprites() { if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER - | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) { + | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID + | DIRTY_ICON_STYLE))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -274,6 +275,21 @@ void SpriteController::doUpdateSprites() { update.state.transformationMatrix.dtdy); } + if (wantSurfaceVisibleAndDrawn + && (becomingVisible + || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) { + Parcel p; + p.writeInt32(update.state.icon.style); + p.writeFloat(update.state.icon.hotSpotX); + p.writeFloat(update.state.icon.hotSpotY); + + // Pass cursor metadata in the sprite surface so that when Android is running as a + // client OS (e.g. ARC++) the host OS can get the requested cursor metadata and + // update mouse cursor in the host OS. + t.setMetadata( + update.state.surfaceControl, METADATA_MOUSE_CURSOR, p); + } + int32_t surfaceLayer = mOverlayLayer + update.state.layer; if (wantSurfaceVisibleAndDrawn && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) { @@ -397,9 +413,14 @@ void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { } else { dirty = DIRTY_BITMAP; } + + if (mLocked.state.icon.style != icon.style) { + mLocked.state.icon.style = icon.style; + dirty |= DIRTY_ICON_STYLE; + } } else if (mLocked.state.icon.isValid()) { mLocked.state.icon.bitmap.reset(); - dirty = DIRTY_BITMAP | DIRTY_HOTSPOT; + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE; } else { return; // setting to invalid icon and already invalid so nothing to do } diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 5b216f50d113..79a904f5fe65 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -55,11 +55,12 @@ struct SpriteTransformationMatrix { * Icon that a sprite displays, including its hotspot. */ struct SpriteIcon { - inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { } - inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) : - bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { } + inline SpriteIcon() : style(0), hotSpotX(0), hotSpotY(0) { } + inline SpriteIcon(const SkBitmap& bitmap, int32_t style, float hotSpotX, float hotSpotY) : + bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { } SkBitmap bitmap; + int32_t style; float hotSpotX; float hotSpotY; @@ -69,11 +70,12 @@ struct SpriteIcon { bitmap.readPixels(bitmapCopy.info(), bitmapCopy.getPixels(), bitmapCopy.rowBytes(), 0, 0); } - return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY); + return SpriteIcon(bitmapCopy, style, hotSpotX, hotSpotY); } inline void reset() { bitmap.reset(); + style = 0; hotSpotX = 0; hotSpotY = 0; } @@ -149,15 +151,15 @@ public: SpriteController(const sp<Looper>& looper, int32_t overlayLayer); /* Creates a new sprite, initially invisible. */ - sp<Sprite> createSprite(); + virtual sp<Sprite> createSprite(); /* Opens or closes a transaction to perform a batch of sprite updates as part of * a single operation such as setPosition and setAlpha. It is not necessary to * open a transaction when updating a single property. * Calls to openTransaction() nest and must be matched by an equal number * of calls to closeTransaction(). */ - void openTransaction(); - void closeTransaction(); + virtual void openTransaction(); + virtual void closeTransaction(); private: enum { @@ -174,6 +176,7 @@ private: DIRTY_VISIBILITY = 1 << 5, DIRTY_HOTSPOT = 1 << 6, DIRTY_DISPLAY_ID = 1 << 7, + DIRTY_ICON_STYLE = 1 << 8, }; /* Describes the state of a sprite. diff --git a/libs/input/TEST_MAPPING b/libs/input/TEST_MAPPING new file mode 100644 index 000000000000..fe74c62d4ec1 --- /dev/null +++ b/libs/input/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "libinputservice_test" + } + ] +} diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp new file mode 100644 index 000000000000..e83b2a78d180 --- /dev/null +++ b/libs/input/tests/Android.bp @@ -0,0 +1,45 @@ +// Copyright (C) 2019 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. + +cc_test { + name: "libinputservice_test", + srcs: [ + "PointerController_test.cpp", + ], + shared_libs: [ + "libinputservice", + "libgui", + "libhwui", + "libutils", + ], + static_libs: [ + "libgmock", + "libgtest", + ], + header_libs: [ + "libbase_headers", + "libinputflinger_headers", + ], + include_dirs: [ + "frameworks/base/libs", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + ], + test_suites: [ + "general-tests", + ], +} diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp new file mode 100644 index 000000000000..92efb4ea86ff --- /dev/null +++ b/libs/input/tests/PointerController_test.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "mocks/MockSprite.h" +#include "mocks/MockSpriteController.h" + +#include <input/PointerController.h> +#include <input/SpriteController.h> + +#include <atomic> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <thread> + +namespace android { + +enum TestCursorType { + CURSOR_TYPE_DEFAULT = 0, + CURSOR_TYPE_HOVER, + CURSOR_TYPE_TOUCH, + CURSOR_TYPE_ANCHOR, + CURSOR_TYPE_ADDITIONAL_1, + CURSOR_TYPE_ADDITIONAL_2, + CURSOR_TYPE_CUSTOM = -1, +}; + +using ::testing::AllOf; +using ::testing::Field; +using ::testing::NiceMock; +using ::testing::Mock; +using ::testing::Return; +using ::testing::Test; + +std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) { + return std::make_pair(type * 10, type * 10 + 5); +} + +class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface { +public: + virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override; + virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override; + virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources, + std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override; + virtual int32_t getDefaultPointerIconId() override; + virtual int32_t getCustomPointerIconId() override; + +private: + void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType); +}; + +void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { + loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT); +} + +void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources, + int32_t) { + loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER); + loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH); + loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR); +} + +void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( + std::map<int32_t, SpriteIcon>* outResources, + std::map<int32_t, PointerAnimation>* outAnimationResources, + int32_t) { + SpriteIcon icon; + PointerAnimation anim; + + for (int32_t cursorType : {CURSOR_TYPE_ADDITIONAL_1, CURSOR_TYPE_ADDITIONAL_2}) { + loadPointerIconForType(&icon, cursorType); + anim.animationFrames.push_back(icon); + anim.durationPerFrame = 10; + (*outResources)[cursorType] = icon; + (*outAnimationResources)[cursorType] = anim; + } +} + +int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() { + return CURSOR_TYPE_DEFAULT; +} + +int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() { + return CURSOR_TYPE_CUSTOM; +} + +void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) { + icon->style = type; + std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type); + icon->hotSpotX = hotSpot.first; + icon->hotSpotY = hotSpot.second; +} + +class PointerControllerTest : public Test { +protected: + PointerControllerTest(); + ~PointerControllerTest(); + + sp<MockSprite> mPointerSprite; + sp<MockPointerControllerPolicyInterface> mPolicy; + sp<MockSpriteController> mSpriteController; + sp<PointerController> mPointerController; + +private: + void loopThread(); + + std::atomic<bool> mRunning = true; + class MyLooper : public Looper { + public: + MyLooper() : Looper(false) {} + ~MyLooper() = default; + }; + sp<MyLooper> mLooper; + std::thread mThread; +}; + +PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>), + mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) { + + mSpriteController = new NiceMock<MockSpriteController>(mLooper); + mPolicy = new MockPointerControllerPolicyInterface(); + + EXPECT_CALL(*mSpriteController, createSprite()) + .WillOnce(Return(mPointerSprite)); + + mPointerController = new PointerController(mPolicy, mLooper, mSpriteController); + + DisplayViewport viewport; + viewport.displayId = ADISPLAY_ID_DEFAULT; + viewport.logicalRight = 1600; + viewport.logicalBottom = 1200; + viewport.physicalRight = 800; + viewport.physicalBottom = 600; + viewport.deviceWidth = 400; + viewport.deviceHeight = 300; + mPointerController->setDisplayViewport(viewport); +} + +PointerControllerTest::~PointerControllerTest() { + mRunning.store(false, std::memory_order_relaxed); + mThread.join(); +} + +void PointerControllerTest::loopThread() { + Looper::setForThread(mLooper); + + while (mRunning.load(std::memory_order_relaxed)) { + mLooper->pollOnce(100); + } +} + +TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + + std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT); + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, setIcon( + AllOf( + Field(&SpriteIcon::style, CURSOR_TYPE_DEFAULT), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); + mPointerController->reloadPointerResources(); +} + +TEST_F(PointerControllerTest, updatePointerIcon) { + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + + int32_t type = CURSOR_TYPE_ADDITIONAL_1; + std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type); + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, setIcon( + AllOf( + Field(&SpriteIcon::style, type), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); + mPointerController->updatePointerIcon(type); +} + +TEST_F(PointerControllerTest, setCustomPointerIcon) { + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + + int32_t style = CURSOR_TYPE_CUSTOM; + float hotSpotX = 15; + float hotSpotY = 20; + + SpriteIcon icon; + icon.style = style; + icon.hotSpotX = hotSpotX; + icon.hotSpotY = hotSpotY; + + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, setIcon( + AllOf( + Field(&SpriteIcon::style, style), + Field(&SpriteIcon::hotSpotX, hotSpotX), + Field(&SpriteIcon::hotSpotY, hotSpotY)))); + mPointerController->setCustomPointerIcon(icon); +} + +} // namespace android diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h new file mode 100644 index 000000000000..013b79c3a3bf --- /dev/null +++ b/libs/input/tests/mocks/MockSprite.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 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. + */ + +#ifndef _MOCK_SPRITE_H +#define _MOCK_SPRITE_H + +#include <input/SpriteController.h> + +#include <gmock/gmock.h> + +namespace android { + +class MockSprite : public Sprite { +public: + virtual ~MockSprite() = default; + + MOCK_METHOD(void, setIcon, (const SpriteIcon& icon), (override)); + MOCK_METHOD(void, setVisible, (bool), (override)); + MOCK_METHOD(void, setPosition, (float, float), (override)); + MOCK_METHOD(void, setLayer, (int32_t), (override)); + MOCK_METHOD(void, setAlpha, (float), (override)); + MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override)); + MOCK_METHOD(void, setDisplayId, (int32_t), (override)); +}; + +} // namespace android + +#endif // _MOCK_SPRITE_H diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h new file mode 100644 index 000000000000..a034f66c9abf --- /dev/null +++ b/libs/input/tests/mocks/MockSpriteController.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 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. + */ + +#ifndef _MOCK_SPRITE_CONTROLLER_H +#define _MOCK_SPRITE_CONTROLLER_H + +#include "MockSprite.h" + +#include <input/SpriteController.h> + +namespace android { + +class MockSpriteController : public SpriteController { + +public: + MockSpriteController(sp<Looper> looper) : SpriteController(looper, 0) {} + ~MockSpriteController() {} + + MOCK_METHOD(sp<Sprite>, createSprite, (), (override)); + MOCK_METHOD(void, openTransaction, (), (override)); + MOCK_METHOD(void, closeTransaction, (), (override)); +}; + +} // namespace android + +#endif // _MOCK_SPRITE_CONTROLLER_H diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 148ffafad491..55583d58e0c2 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -37,6 +37,7 @@ import libcore.io.IoUtils; import libcore.io.Streams; import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.DataInput; import java.io.DataInputStream; @@ -1866,14 +1867,17 @@ public class ExifInterface { FileInputStream in = null; FileOutputStream out = null; + File originalFile = null; + if (mFilename != null) { + originalFile = new File(mFilename); + } File tempFile = null; try { // Move the original file to temporary file. if (mFilename != null) { tempFile = new File(mFilename + ".tmp"); - File originalFile = new File(mFilename); if (!originalFile.renameTo(tempFile)) { - throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath()); + throw new IOException("Couldn't rename to " + tempFile.getAbsolutePath()); } } else if (mSeekableFileDescriptor != null) { tempFile = File.createTempFile("temp", "jpg"); @@ -1882,8 +1886,8 @@ public class ExifInterface { out = new FileOutputStream(tempFile); Streams.copy(in, out); } - } catch (ErrnoException e) { - throw e.rethrowAsIOException(); + } catch (Exception e) { + throw new IOException("Failed to copy original file to temp file", e); } finally { IoUtils.closeQuietly(in); IoUtils.closeQuietly(out); @@ -1900,9 +1904,18 @@ public class ExifInterface { Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET); out = new FileOutputStream(mSeekableFileDescriptor); } - saveJpegAttributes(in, out); - } catch (ErrnoException e) { - throw e.rethrowAsIOException(); + try (BufferedInputStream bufferedIn = new BufferedInputStream(in); + BufferedOutputStream bufferedOut = new BufferedOutputStream(out)) { + saveJpegAttributes(bufferedIn, bufferedOut); + } + } catch (Exception e) { + if (mFilename != null) { + if (!tempFile.renameTo(originalFile)) { + throw new IOException("Couldn't restore original file: " + + originalFile.getAbsolutePath()); + } + } + throw new IOException("Failed to save new file", e); } finally { IoUtils.closeQuietly(in); IoUtils.closeQuietly(out); diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 471fa2c4bad9..7eec8d9f6cc3 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -16,17 +16,20 @@ package android.media; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; +import android.content.ContentProviderClient; import android.content.Context; -import android.content.Intent; import android.content.ServiceConnection; -import android.media.IMediaScannerListener; -import android.media.IMediaScannerService; import android.net.Uri; +import android.os.Build; import android.os.IBinder; -import android.os.RemoteException; +import android.provider.MediaStore; import android.util.Log; +import com.android.internal.os.BackgroundThread; + +import java.io.File; /** * MediaScannerConnection provides a way for applications to pass a @@ -38,20 +41,24 @@ import android.util.Log; * to the client of the MediaScannerConnection class. */ public class MediaScannerConnection implements ServiceConnection { - private static final String TAG = "MediaScannerConnection"; - private Context mContext; - private MediaScannerConnectionClient mClient; - private IMediaScannerService mService; - private boolean mConnected; // true if connect() has been called since last disconnect() + private final Context mContext; + private final MediaScannerConnectionClient mClient; + private ContentProviderClient mProvider; + + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) + private IMediaScannerService mService; + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) + private boolean mConnected; + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() { + @Override public void scanCompleted(String path, Uri uri) { - MediaScannerConnectionClient client = mClient; - if (client != null) { - client.onScanCompleted(path, uri); - } } }; @@ -81,15 +88,6 @@ public class MediaScannerConnection implements ServiceConnection { * MediaScanner service has been established. */ public void onMediaScannerConnected(); - - /** - * Called to notify the client when the media scanner has finished - * scanning a file. - * @param path the path to the file that has been scanned. - * @param uri the Uri for the file if the scanning operation succeeded - * and the file was added to the media database, or null if scanning failed. - */ - public void onScanCompleted(String path, Uri uri); } /** @@ -111,13 +109,12 @@ public class MediaScannerConnection implements ServiceConnection { */ public void connect() { synchronized (this) { - if (!mConnected) { - Intent intent = new Intent(IMediaScannerService.class.getName()); - intent.setComponent( - new ComponentName("com.android.providers.media", - "com.android.providers.media.MediaScannerService")); - mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); - mConnected = true; + if (mProvider == null) { + mProvider = mContext.getContentResolver() + .acquireContentProviderClient(MediaStore.AUTHORITY); + if (mClient != null) { + mClient.onMediaScannerConnected(); + } } } } @@ -127,22 +124,9 @@ public class MediaScannerConnection implements ServiceConnection { */ public void disconnect() { synchronized (this) { - if (mConnected) { - if (false) { - Log.v(TAG, "Disconnecting from Media Scanner"); - } - try { - mContext.unbindService(this); - if (mClient instanceof ClientProxy) { - mClient = null; - } - mService = null; - } catch (IllegalArgumentException ex) { - if (false) { - Log.v(TAG, "disconnect failed: " + ex); - } - } - mConnected = false; + if (mProvider != null) { + mProvider.close(); + mProvider = null; } } } @@ -152,7 +136,7 @@ public class MediaScannerConnection implements ServiceConnection { * @return true if we are connected, false otherwise */ public synchronized boolean isConnected() { - return (mService != null && mConnected); + return (mProvider != null); } /** @@ -166,107 +150,107 @@ public class MediaScannerConnection implements ServiceConnection { */ public void scanFile(String path, String mimeType) { synchronized (this) { - if (mService == null || !mConnected) { + if (mProvider == null) { throw new IllegalStateException("not connected to MediaScannerService"); } - try { - if (false) { - Log.v(TAG, "Scanning file " + path); + BackgroundThread.getExecutor().execute(() -> { + final Uri uri = scanFileQuietly(mProvider, new File(path)); + if (mClient != null) { + mClient.onScanCompleted(path, uri); } - mService.requestScanFile(path, mimeType, mListener); - } catch (RemoteException e) { - if (false) { - Log.d(TAG, "Failed to scan file " + path); + }); + } + } + + /** + * Convenience for constructing a {@link MediaScannerConnection}, calling + * {@link #connect} on it, and calling {@link #scanFile} with the given + * <var>path</var> and <var>mimeType</var> when the connection is + * established. + * @param context The caller's Context, required for establishing a connection to + * the media scanner service. + * Success or failure of the scanning operation cannot be determined until + * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. + * @param paths Array of paths to be scanned. + * @param mimeTypes Optional array of MIME types for each path. + * If mimeType is null, then the mimeType will be inferred from the file extension. + * @param callback Optional callback through which you can receive the + * scanned URI and MIME type; If null, the file will be scanned but + * you will not get a result back. + * @see #scanFile(String, String) + */ + public static void scanFile(Context context, String[] paths, String[] mimeTypes, + OnScanCompletedListener callback) { + BackgroundThread.getExecutor().execute(() -> { + try (ContentProviderClient client = context.getContentResolver() + .acquireContentProviderClient(MediaStore.AUTHORITY)) { + for (String path : paths) { + final Uri uri = scanFileQuietly(client, new File(path)); + if (callback != null) { + callback.onScanCompleted(path, uri); + } } } + }); + } + + private static Uri scanFileQuietly(ContentProviderClient client, File file) { + Uri uri = null; + try { + uri = MediaStore.scanFile(client, file); + Log.d(TAG, "Scanned " + file + " to " + uri); + } catch (Exception e) { + Log.w(TAG, "Failed to scan " + file + ": " + e); } + return uri; } + @Deprecated static class ClientProxy implements MediaScannerConnectionClient { + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mPaths; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mMimeTypes; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final OnScanCompletedListener mClient; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) MediaScannerConnection mConnection; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) int mNextPath; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) { mPaths = paths; mMimeTypes = mimeTypes; mClient = client; } + @Override public void onMediaScannerConnected() { - scanNextPath(); } + @Override public void onScanCompleted(String path, Uri uri) { - if (mClient != null) { - mClient.onScanCompleted(path, uri); - } - scanNextPath(); } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) void scanNextPath() { - if (mNextPath >= mPaths.length) { - mConnection.disconnect(); - mConnection = null; - return; - } - String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null; - mConnection.scanFile(mPaths[mNextPath], mimeType); - mNextPath++; } } /** - * Convenience for constructing a {@link MediaScannerConnection}, calling - * {@link #connect} on it, and calling {@link #scanFile} with the given - * <var>path</var> and <var>mimeType</var> when the connection is - * established. - * @param context The caller's Context, required for establishing a connection to - * the media scanner service. - * Success or failure of the scanning operation cannot be determined until - * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. - * @param paths Array of paths to be scanned. - * @param mimeTypes Optional array of MIME types for each path. - * If mimeType is null, then the mimeType will be inferred from the file extension. - * @param callback Optional callback through which you can receive the - * scanned URI and MIME type; If null, the file will be scanned but - * you will not get a result back. - * @see #scanFile(String, String) - */ - public static void scanFile(Context context, String[] paths, String[] mimeTypes, - OnScanCompletedListener callback) { - ClientProxy client = new ClientProxy(paths, mimeTypes, callback); - MediaScannerConnection connection = new MediaScannerConnection(context, client); - client.mConnection = connection; - connection.connect(); - } - - /** * Part of the ServiceConnection interface. Do not call. */ + @Override public void onServiceConnected(ComponentName className, IBinder service) { - if (false) { - Log.v(TAG, "Connected to Media Scanner"); - } - synchronized (this) { - mService = IMediaScannerService.Stub.asInterface(service); - if (mService != null && mClient != null) { - mClient.onMediaScannerConnected(); - } - } + // No longer needed } /** * Part of the ServiceConnection interface. Do not call. */ + @Override public void onServiceDisconnected(ComponentName className) { - if (false) { - Log.v(TAG, "Disconnected from Media Scanner"); - } - synchronized (this) { - mService = null; - } + // No longer needed } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 97fbea6ea237..7ea83f56557a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -32,7 +32,10 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.inputmethodservice.InputMethodService; +import android.os.IBinder; import android.util.Log; +import android.view.Display; import android.view.GestureDetector; import android.view.Gravity; import android.view.MotionEvent; @@ -87,8 +90,7 @@ import java.util.Map; /** * A status bar (and navigation bar) tailored for the automotive use case. */ -public class CarStatusBar extends StatusBar implements - CarBatteryController.BatteryViewHandler { +public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler { private static final String TAG = "CarStatusBar"; // used to calculate how fast to open or close the window private static final float DEFAULT_FLING_VELOCITY = 0; @@ -169,6 +171,9 @@ public class CarStatusBar extends StatusBar implements private boolean mIsSwipingVerticallyToClose; // Whether heads-up notifications should be shown when shade is open. private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen; + // If the nav bar should be hidden when the soft keyboard is visible. + private boolean mHideNavBarForKeyboard; + private boolean mBottomNavBarVisible; private final CarPowerStateListener mCarPowerStateListener = (int state) -> { @@ -190,6 +195,17 @@ public class CarStatusBar extends StatusBar implements // builds the nav bar mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned(); + + // Keyboard related setup, before nav bars are created. + mHideNavBarForKeyboard = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard); + mBottomNavBarVisible = false; + + // Need to initialize screen lifecycle before calling super.start - before switcher is + // created. + mScreenLifecycle = Dependency.get(ScreenLifecycle.class); + mScreenLifecycle.addObserver(mScreenObserver); + super.start(); mTaskStackListener = new TaskStackListenerImpl(); mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); @@ -236,9 +252,6 @@ public class CarStatusBar extends StatusBar implements mPowerManagerHelper.connectToCarService(); mSwitchToGuestTimer = new SwitchToGuestTimer(mContext); - - mScreenLifecycle = Dependency.get(ScreenLifecycle.class); - mScreenLifecycle.addObserver(mScreenObserver); } /** @@ -720,6 +733,13 @@ public class CarStatusBar extends StatusBar implements buildNavBarContent(); attachNavBarWindows(); + // Try setting up the initial state of the nav bar if applicable. + if (result != null) { + setImeWindowStatus(Display.DEFAULT_DISPLAY, result.mImeToken, + result.mImeWindowVis, result.mImeBackDisposition, + result.mShowImeSwitcher); + } + // There has been a car customized nav bar on the default display, so just create nav bars // on external displays. mNavigationBarController.createNavigationBars(false /* includeDefaultDisplay */, result); @@ -758,22 +778,33 @@ public class CarStatusBar extends StatusBar implements } - private void attachNavBarWindows() { + /** + * We register for soft keyboard visibility events such that we can hide the navigation bar + * giving more screen space to the IME. Note: this is optional and controlled by + * {@code com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard}. + */ + @Override + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, + boolean showImeSwitcher) { + if (!mHideNavBarForKeyboard) { + return; + } - if (mShowBottom) { - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - lp.setTitle("CarNavigationBar"); - lp.windowAnimations = 0; - mWindowManager.addView(mNavigationBarWindow, lp); + if (mContext.getDisplay().getDisplayId() != displayId) { + return; } + boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0; + if (!isKeyboardVisible) { + attachBottomNavBarWindow(); + } else { + detachBottomNavBarWindow(); + } + } + + private void attachNavBarWindows() { + attachBottomNavBarWindow(); + if (mShowLeft) { int width = mContext.getResources().getDimensionPixelSize( R.dimen.car_left_navigation_bar_width); @@ -808,7 +839,49 @@ public class CarStatusBar extends StatusBar implements rightlp.gravity = Gravity.RIGHT; mWindowManager.addView(mRightNavigationBarWindow, rightlp); } + } + + /** + * Attaches the bottom nav bar window. Can be extended to modify the specific behavior of + * attaching the bottom nav bar. + */ + protected void attachBottomNavBarWindow() { + if (!mShowBottom) { + return; + } + if (mBottomNavBarVisible) { + return; + } + mBottomNavBarVisible = true; + + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + lp.setTitle("CarNavigationBar"); + lp.windowAnimations = 0; + mWindowManager.addView(mNavigationBarWindow, lp); + } + + /** + * Detaches the bottom nav bar window. Can be extended to modify the specific behavior of + * detaching the bottom nav bar. + */ + protected void detachBottomNavBarWindow() { + if (!mShowBottom) { + return; + } + + if (!mBottomNavBarVisible) { + return; + } + mBottomNavBarVisible = false; + mWindowManager.removeView(mNavigationBarWindow); } private void buildBottomBar(int layout) { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index 077f7ecd3e46..cf286bdbde96 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -20,6 +20,8 @@ import android.content.Context; import android.gsi.GsiProgress; import android.net.Uri; import android.os.AsyncTask; +import android.os.MemoryFile; +import android.os.ParcelFileDescriptor; import android.os.image.DynamicSystemManager; import android.util.Log; import android.webkit.URLUtil; @@ -28,11 +30,9 @@ import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.Arrays; import java.util.Locale; import java.util.zip.GZIPInputStream; - class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { private static final String TAG = "InstallationAsyncTask"; @@ -125,28 +125,26 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { Thread.sleep(10); } - if (mInstallationSession == null) { - throw new IOException("Failed to start installation with requested size: " - + (mSystemSize + mUserdataSize)); + throw new IOException( + "Failed to start installation with requested size: " + + (mSystemSize + mUserdataSize)); } installedSize = mUserdataSize; + MemoryFile memoryFile = new MemoryFile("dsu", READ_BUFFER_SIZE); byte[] bytes = new byte[READ_BUFFER_SIZE]; - + mInstallationSession.setAshmem( + new ParcelFileDescriptor(memoryFile.getFileDescriptor()), READ_BUFFER_SIZE); int numBytesRead; - Log.d(TAG, "Start installation loop"); while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) { + memoryFile.writeBytes(bytes, 0, 0, numBytesRead); if (isCancelled()) { break; } - - byte[] writeBuffer = numBytesRead == READ_BUFFER_SIZE - ? bytes : Arrays.copyOf(bytes, numBytesRead); - - if (!mInstallationSession.write(writeBuffer)) { + if (!mInstallationSession.submitFromAshmem(numBytesRead)) { throw new IOException("Failed write() to DynamicSystem"); } @@ -157,7 +155,6 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { reportedInstalledSize = installedSize; } } - return null; } catch (Exception e) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 2286f4c80a81..36e945fe30b6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -34,7 +34,7 @@ import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.provider.Settings; -import android.provider.SettingsValidators.Validator; +import android.provider.settings.validators.Validator; import android.util.ArrayMap; import android.util.ArraySet; import android.util.BackupUtils; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index a3b0e6be7a4f..00b2563f559b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1538,9 +1538,6 @@ class SettingsProtoDumpUtil { Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, GlobalSettingsProto.Wifi.NETWORKS_AVAILABLE_NOTIFICATION_ON); dumpSetting(s, p, - Settings.Global.WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON, - GlobalSettingsProto.Wifi.CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON); - dumpSetting(s, p, Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, GlobalSettingsProto.Wifi.NETWORKS_AVAILABLE_REPEAT_DELAY); dumpSetting(s, p, @@ -1607,9 +1604,6 @@ class SettingsProtoDumpUtil { Settings.Global.WIFI_P2P_DEVICE_NAME, GlobalSettingsProto.Wifi.P2P_DEVICE_NAME); dumpSetting(s, p, - Settings.Global.WIFI_REENABLE_DELAY_MS, - GlobalSettingsProto.Wifi.REENABLE_DELAY_MS); - dumpSetting(s, p, Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS, GlobalSettingsProto.Wifi.EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS); dumpSetting(s, p, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 7016d3067687..e492e28f6172 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -70,7 +70,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; -import android.provider.SettingsValidators; +import android.provider.settings.validators.Validator; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -314,11 +314,6 @@ public class SettingsProvider extends ContentProvider { public boolean onCreate() { Settings.setInSystemServer(); - // fail to boot if there're any backed up settings that don't have a non-null validator - ensureAllBackedUpSystemSettingsHaveValidators(); - ensureAllBackedUpGlobalSettingsHaveValidators(); - ensureAllBackedUpSecureSettingsHaveValidators(); - synchronized (mLock) { mUserManager = UserManager.get(getContext()); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -338,57 +333,6 @@ public class SettingsProvider extends ContentProvider { return true; } - private void ensureAllBackedUpSystemSettingsHaveValidators() { - String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP, - Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS); - - failToBootIfOffendersPresent(offenders, "Settings.System"); - } - - private void ensureAllBackedUpGlobalSettingsHaveValidators() { - String offenders = getOffenders(concat(Settings.Global.SETTINGS_TO_BACKUP, - Settings.Global.LEGACY_RESTORE_SETTINGS), Settings.Global.VALIDATORS); - - failToBootIfOffendersPresent(offenders, "Settings.Global"); - } - - private void ensureAllBackedUpSecureSettingsHaveValidators() { - String offenders = getOffenders(concat(Settings.Secure.SETTINGS_TO_BACKUP, - Settings.Secure.LEGACY_RESTORE_SETTINGS), Settings.Secure.VALIDATORS); - - failToBootIfOffendersPresent(offenders, "Settings.Secure"); - } - - private void failToBootIfOffendersPresent(String offenders, String settingsType) { - if (offenders.length() > 0) { - throw new RuntimeException("All " + settingsType + " settings that are backed up" - + " have to have a non-null validator, but those don't: " + offenders); - } - } - - private String getOffenders(String[] settingsToBackup, Map<String, - SettingsValidators.Validator> validators) { - StringBuilder offenders = new StringBuilder(); - for (String setting : settingsToBackup) { - if (validators.get(setting) == null) { - offenders.append(setting).append(" "); - } - } - return offenders.toString(); - } - - private final String[] concat(String[] first, String[] second) { - if (second == null || second.length == 0) { - return first; - } - final int firstLen = first.length; - final int secondLen = second.length; - String[] both = new String[firstLen + secondLen]; - System.arraycopy(first, 0, both, 0, firstLen); - System.arraycopy(second, 0, both, firstLen, secondLen); - return both; - } - @Override public Bundle call(String method, String name, Bundle args) { final int requestingUserId = getRequestingUserId(args); @@ -1773,7 +1717,7 @@ public class SettingsProvider extends ContentProvider { } private void validateSystemSettingValue(String name, String value) { - SettingsValidators.Validator validator = Settings.System.VALIDATORS.get(name); + Validator validator = Settings.System.VALIDATORS.get(name); if (validator != null && !validator.validate(value)) { throw new IllegalArgumentException("Invalid value: " + value + " for setting: " + name); diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 58e1d47e5379..8c0108dace69 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -352,10 +352,10 @@ public class BugreportProgressService extends Service { private final BugreportInfo mInfo; - BugreportCallbackImpl(String name) { + BugreportCallbackImpl(String name, @Nullable String title, @Nullable String description) { // pid not used in this workflow, so setting default = 0 mInfo = new BugreportInfo(mContext, 0 /* pid */, name, - 100 /* max progress*/); + 100 /* max progress*/, title, description); } @Override @@ -578,6 +578,8 @@ public class BugreportProgressService extends Service { } int bugreportType = intent.getIntExtra(EXTRA_BUGREPORT_TYPE, BugreportParams.BUGREPORT_MODE_INTERACTIVE); + String shareTitle = intent.getStringExtra(EXTRA_TITLE); + String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION); ParcelFileDescriptor screenshotFd = createReadWriteFile(BUGREPORT_DIR, bugreportName + ".png"); @@ -595,7 +597,8 @@ public class BugreportProgressService extends Service { + " bugreport file fd: " + bugreportFd + " screenshot file fd: " + screenshotFd); - BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(bugreportName); + BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(bugreportName, + shareTitle, shareDescription); try { mBugreportManager.startBugreport(bugreportFd, screenshotFd, new BugreportParams(bugreportType), executor, bugreportCallback); @@ -982,7 +985,10 @@ public class BugreportProgressService extends Service { } screenshotFile = null; } - onBugreportFinished(id, bugreportFile, screenshotFile, info.title, info.description, max); + // TODO: Since we are passing id to the function, it should be able to find the info linked + // to the id and therefore use the value of shareTitle and shareDescription. + onBugreportFinished(id, bugreportFile, screenshotFile, info.shareTitle, + info.shareDescription, max); } @@ -1844,6 +1850,14 @@ public class BugreportProgressService extends Service { String title; /** + * One-line summary of the bug; when set, will be used as the subject of the + * {@link Intent#ACTION_SEND_MULTIPLE} intent. This is the predefined title which is + * set initially when the request to take a bugreport is made. This overrides any changes + * in the title that the user makes after the bugreport starts. + */ + String shareTitle; + + /** * User-provided, detailed description of the bugreport; when set, will be added to the body * of the {@link Intent#ACTION_SEND_MULTIPLE} intent. */ @@ -1906,7 +1920,9 @@ public class BugreportProgressService extends Service { int screenshotCounter; /** - * Descriptive text that will be shown to the user in the notification message. + * Descriptive text that will be shown to the user in the notification message. This is the + * predefined description which is set initially when the request to take a bugreport is + * made. */ String shareDescription; @@ -1914,18 +1930,21 @@ public class BugreportProgressService extends Service { * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED. */ BugreportInfo(Context context, int id, int pid, String name, int max) { - this(context, pid, name, max); + this(context, pid, name, max, null, null); this.id = id; } /** * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED. */ - BugreportInfo(Context context, int pid, String name, int max) { + BugreportInfo(Context context, int pid, String name, int max, @Nullable String shareTitle, + @Nullable String shareDescription) { this.context = context; this.pid = pid; this.name = name; this.max = this.realMax = max; + this.shareTitle = shareTitle == null ? "" : shareTitle; + this.shareDescription = shareDescription == null ? "" : shareDescription; } /** @@ -2019,6 +2038,7 @@ public class BugreportProgressService extends Service { .append("\n\taddingDetailsToZip: ").append(addingDetailsToZip) .append(" addedDetailsToZip: ").append(addedDetailsToZip) .append("\n\tshareDescription: ").append(shareDescription) + .append("\n\tshareTitle: ").append(shareTitle) .toString(); } @@ -2046,6 +2066,7 @@ public class BugreportProgressService extends Service { finished = in.readInt() == 1; screenshotCounter = in.readInt(); shareDescription = in.readString(); + shareTitle = in.readString(); } @Override @@ -2071,6 +2092,7 @@ public class BugreportProgressService extends Service { dest.writeInt(finished ? 1 : 0); dest.writeInt(screenshotCounter); dest.writeString(shareDescription); + dest.writeString(shareTitle); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index de20972347d5..8c5374a2a3fe 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1446,6 +1446,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { protected void handleStartedGoingToSleep(int arg1) { checkIsHandlerThread(); + mLockIconPressed = false; clearBiometricRecognized(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -1481,7 +1482,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private void handleScreenTurnedOff() { checkIsHandlerThread(); - mLockIconPressed = false; mHardwareFingerprintUnavailableRetryCount = 0; mHardwareFaceUnavailableRetryCount = 0; for (int i = 0; i < mCallbacks.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java new file mode 100644 index 000000000000..d4baefd64512 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.hardware.biometrics.BiometricPrompt; +import android.os.Bundle; +import android.view.WindowManager; + +import com.android.systemui.biometrics.ui.BiometricDialogView; + +/** + * Interface for the biometric dialog UI. + */ +public interface BiometricDialog { + + // TODO: Clean up save/restore state + String[] KEYS_TO_BACKUP = { + BiometricPrompt.KEY_TITLE, + BiometricPrompt.KEY_USE_DEFAULT_TITLE, + BiometricPrompt.KEY_SUBTITLE, + BiometricPrompt.KEY_DESCRIPTION, + BiometricPrompt.KEY_POSITIVE_TEXT, + BiometricPrompt.KEY_NEGATIVE_TEXT, + BiometricPrompt.KEY_REQUIRE_CONFIRMATION, + BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, + BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, + + BiometricDialogView.KEY_TRY_AGAIN_VISIBILITY, + BiometricDialogView.KEY_CONFIRM_VISIBILITY, + BiometricDialogView.KEY_CONFIRM_ENABLED, + BiometricDialogView.KEY_STATE, + BiometricDialogView.KEY_ERROR_TEXT_VISIBILITY, + BiometricDialogView.KEY_ERROR_TEXT_STRING, + BiometricDialogView.KEY_ERROR_TEXT_IS_TEMPORARY, + BiometricDialogView.KEY_ERROR_TEXT_COLOR, + }; + + /** + * Show the dialog. + * @param wm + * @param skipIntroAnimation + */ + void show(WindowManager wm, boolean skipIntroAnimation); + + /** + * Dismiss the dialog without sending a callback. + */ + void dismissWithoutCallback(boolean animate); + + /** + * Dismiss the dialog. Animate away. + */ + void dismissFromSystemServer(); + + /** + * Biometric authenticated. May be pending user confirmation, or completed. + */ + void onAuthenticationSucceeded(); + + /** + * Authentication failed (reject, timeout). Dialog stays showing. + * @param failureReason + */ + void onAuthenticationFailed(String failureReason); + + /** + * Authentication rejected, or help message received. + * @param help + */ + void onHelp(String help); + + /** + * Authentication failed. Dialog going away. + * @param error + */ + void onError(String error); + + /** + * Save the current state. + * @param outState + */ + void onSaveState(Bundle outState); + + /** + * Restore a previous state. + * @param savedState + */ + void restoreState(Bundle savedState); + + /** + * Get the client's package name + */ + String getOpPackageName(); +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index e66a8fa96298..a8e572216315 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -16,135 +16,154 @@ package com.android.systemui.biometrics; +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.view.WindowManager; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; -import com.android.systemui.Dependency; import com.android.systemui.SystemUI; -import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.biometrics.ui.BiometricDialogView; import com.android.systemui.statusbar.CommandQueue; +import java.util.List; + /** - * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g. - * BiometricDialogView). + * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the + * appropriate biometric UI (e.g. BiometricDialogView). */ -public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks { +public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks, + DialogViewCallback { private static final String TAG = "BiometricDialogImpl"; private static final boolean DEBUG = true; - private static final int MSG_SHOW_DIALOG = 1; - private static final int MSG_BIOMETRIC_AUTHENTICATED = 2; - private static final int MSG_BIOMETRIC_HELP = 3; - private static final int MSG_BIOMETRIC_ERROR = 4; - private static final int MSG_HIDE_DIALOG = 5; - private static final int MSG_BUTTON_NEGATIVE = 6; - private static final int MSG_USER_CANCELED = 7; - private static final int MSG_BUTTON_POSITIVE = 8; - private static final int MSG_TRY_AGAIN_PRESSED = 9; + private final Injector mInjector; + // TODO: These should just be saved from onSaveState private SomeArgs mCurrentDialogArgs; - private BiometricDialogView mCurrentDialog; - private WindowManager mWindowManager; - private IBiometricServiceReceiverInternal mReceiver; - private boolean mDialogShowing; - private Callback mCallback = new Callback(); - private WakefulnessLifecycle mWakefulnessLifecycle; + @VisibleForTesting + BiometricDialog mCurrentDialog; - private Handler mHandler = new Handler(Looper.getMainLooper()) { + private Handler mHandler = new Handler(Looper.getMainLooper()); + private WindowManager mWindowManager; + @VisibleForTesting + IActivityTaskManager mActivityTaskManager; + @VisibleForTesting + BiometricTaskStackListener mTaskStackListener; + @VisibleForTesting + IBiometricServiceReceiverInternal mReceiver; + + public class BiometricTaskStackListener extends TaskStackListener { @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_SHOW_DIALOG: - handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */, - null /* savedState */); - break; - case MSG_BIOMETRIC_AUTHENTICATED: { - SomeArgs args = (SomeArgs) msg.obj; - handleBiometricAuthenticated((boolean) args.arg1 /* authenticated */, - (String) args.arg2 /* failureReason */); - args.recycle(); - break; - } - case MSG_BIOMETRIC_HELP: { - SomeArgs args = (SomeArgs) msg.obj; - handleBiometricHelp((String) args.arg1 /* message */); - args.recycle(); - break; + public void onTaskStackChanged() { + mHandler.post(mTaskStackChangedRunnable); + } + } + + private final Runnable mTaskStackChangedRunnable = () -> { + if (mCurrentDialog != null) { + try { + final String clientPackage = mCurrentDialog.getOpPackageName(); + Log.w(TAG, "Task stack changed, current client: " + clientPackage); + final List<ActivityManager.RunningTaskInfo> runningTasks = + mActivityTaskManager.getTasks(1); + if (!runningTasks.isEmpty()) { + final String topPackage = runningTasks.get(0).topActivity.getPackageName(); + if (!topPackage.contentEquals(clientPackage)) { + Log.w(TAG, "Evicting client due to: " + topPackage); + mCurrentDialog.dismissWithoutCallback(true /* animate */); + mCurrentDialog = null; + mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + mReceiver = null; + } } - case MSG_BIOMETRIC_ERROR: - handleBiometricError((String) msg.obj); - break; - case MSG_HIDE_DIALOG: - handleHideDialog((Boolean) msg.obj); - break; - case MSG_BUTTON_NEGATIVE: - handleButtonNegative(); - break; - case MSG_USER_CANCELED: - handleUserCanceled(); - break; - case MSG_BUTTON_POSITIVE: - handleButtonPositive(); - break; - case MSG_TRY_AGAIN_PRESSED: - handleTryAgainPressed(); - break; - default: - Log.w(TAG, "Unknown message: " + msg.what); - break; + } catch (RemoteException e) { + Log.e(TAG, "Remote exception", e); } } }; - private class Callback implements DialogViewCallback { - @Override - public void onUserCanceled() { - mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget(); + @Override + public void onTryAgainPressed() { + try { + mReceiver.onTryAgainPressed(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when handling try again", e); } + } - @Override - public void onErrorShown() { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HIDE_DIALOG, - false /* userCanceled */), BiometricPrompt.HIDE_DIALOG_DELAY); + @Override + public void onDismissed(@DismissedReason int reason) { + switch (reason) { + case DialogViewCallback.DISMISSED_USER_CANCELED: + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + break; + + case DialogViewCallback.DISMISSED_BUTTON_NEGATIVE: + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE); + break; + + case DialogViewCallback.DISMISSED_BUTTON_POSITIVE: + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED); + break; + + case DialogViewCallback.DISMISSED_AUTHENTICATED: + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED); + break; + + case DialogViewCallback.DISMISSED_ERROR: + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR); + break; + + case DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER: + sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED); + break; + + default: + Log.e(TAG, "Unhandled reason: " + reason); + break; } + } - @Override - public void onNegativePressed() { - mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget(); + private void sendResultAndCleanUp(@DismissedReason int reason) { + if (mReceiver == null) { + Log.e(TAG, "Receiver is null"); + return; } - - @Override - public void onPositivePressed() { - mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget(); + try { + mReceiver.onDialogDismissed(reason); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception", e); } + onDialogDismissed(reason); + } - @Override - public void onTryAgainPressed() { - mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget(); + public static class Injector { + IActivityTaskManager getActivityTaskManager() { + return ActivityTaskManager.getService(); } } - final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { - @Override - public void onStartedGoingToSleep() { - if (mDialogShowing) { - if (DEBUG) Log.d(TAG, "User canceled due to screen off"); - mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget(); - } - } - }; + public BiometricDialogImpl() { + this(new Injector()); + } + + @VisibleForTesting + BiometricDialogImpl(Injector injector) { + mInjector = injector; + } @Override public void start() { @@ -154,30 +173,38 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) { getComponent(CommandQueue.class).addCallback(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); - mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + mActivityTaskManager = mInjector.getActivityTaskManager(); + + try { + mTaskStackListener = new BiometricTaskStackListener(); + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.w(TAG, "Unable to register task stack listener", e); + } } } @Override public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId) { + int type, boolean requireConfirmation, int userId, String opPackageName) { if (DEBUG) { Log.d(TAG, "showBiometricDialog, type: " + type + ", requireConfirmation: " + requireConfirmation); } - // Remove these messages as they are part of the previous client - mHandler.removeMessages(MSG_BIOMETRIC_ERROR); - mHandler.removeMessages(MSG_BIOMETRIC_HELP); - mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED); - mHandler.removeMessages(MSG_HIDE_DIALOG); SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; args.arg2 = receiver; args.argi1 = type; args.arg3 = requireConfirmation; args.argi2 = userId; - mHandler.obtainMessage(MSG_SHOW_DIALOG, args).sendToTarget(); + args.arg4 = opPackageName; + + boolean skipAnimation = false; + if (mCurrentDialog != null) { + Log.w(TAG, "mCurrentDialog: " + mCurrentDialog); + skipAnimation = true; + } + showDialog(args, skipAnimation, null /* savedState */); } @Override @@ -185,185 +212,111 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated + " reason: " + failureReason); - SomeArgs args = SomeArgs.obtain(); - args.arg1 = authenticated; - args.arg2 = failureReason; - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget(); + if (authenticated) { + mCurrentDialog.onAuthenticationSucceeded(); + } else { + mCurrentDialog.onAuthenticationFailed(failureReason); + } } @Override public void onBiometricHelp(String message) { if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message); - SomeArgs args = SomeArgs.obtain(); - args.arg1 = message; - mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget(); + + mCurrentDialog.onHelp(message); } @Override public void onBiometricError(String error) { if (DEBUG) Log.d(TAG, "onBiometricError: " + error); - mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, error).sendToTarget(); + mCurrentDialog.onError(error); } @Override public void hideBiometricDialog() { if (DEBUG) Log.d(TAG, "hideBiometricDialog"); - mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget(); + + mCurrentDialog.dismissFromSystemServer(); } - private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { + private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { mCurrentDialogArgs = args; final int type = args.argi1; + final Bundle biometricPromptBundle = (Bundle) args.arg1; + final boolean requireConfirmation = (boolean) args.arg3; + final int userId = args.argi2; + final String opPackageName = (String) args.arg4; // Create a new dialog but do not replace the current one yet. - BiometricDialogView newDialog; - if (type == BiometricAuthenticator.TYPE_FINGERPRINT) { - newDialog = new FingerprintDialogView(mContext, mCallback); - } else if (type == BiometricAuthenticator.TYPE_FACE) { - newDialog = new FaceDialogView(mContext, mCallback); - } else { + final BiometricDialog newDialog = buildDialog( + biometricPromptBundle, + requireConfirmation, + userId, + type, + opPackageName); + + if (newDialog == null) { Log.e(TAG, "Unsupported type: " + type); return; } - if (DEBUG) Log.d(TAG, "handleShowDialog, " - + " savedState: " + savedState - + " mCurrentDialog: " + mCurrentDialog - + " newDialog: " + newDialog - + " type: " + type); + if (DEBUG) { + Log.d(TAG, "showDialog, " + + " savedState: " + savedState + + " mCurrentDialog: " + mCurrentDialog + + " newDialog: " + newDialog + + " type: " + type); + } if (savedState != null) { // SavedState is only non-null if it's from onConfigurationChanged. Restore the state // even though it may be removed / re-created again newDialog.restoreState(savedState); - } else if (mCurrentDialog != null && mDialogShowing) { + } else if (mCurrentDialog != null) { // If somehow we're asked to show a dialog, the old one doesn't need to be animated // away. This can happen if the app cancels and re-starts auth during configuration // change. This is ugly because we also have to do things on onConfigurationChanged // here. - mCurrentDialog.forceRemove(); + mCurrentDialog.dismissWithoutCallback(false /* animate */); } mReceiver = (IBiometricServiceReceiverInternal) args.arg2; - newDialog.setBundle((Bundle) args.arg1); - newDialog.setRequireConfirmation((boolean) args.arg3); - newDialog.setUserId(args.argi2); - newDialog.setSkipIntro(skipAnimation); mCurrentDialog = newDialog; - mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); - mDialogShowing = true; - } - - private void handleBiometricAuthenticated(boolean authenticated, String failureReason) { - if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated); - - if (authenticated) { - mCurrentDialog.announceForAccessibility( - mContext.getResources() - .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); - if (mCurrentDialog.requiresConfirmation()) { - mCurrentDialog.updateState(BiometricDialogView.STATE_PENDING_CONFIRMATION); - } else { - mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); - mHandler.postDelayed(() -> { - handleHideDialog(false /* userCanceled */); - }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs()); - } - } else { - mCurrentDialog.onAuthenticationFailed(failureReason); - } - } - - private void handleBiometricHelp(String message) { - if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message); - mCurrentDialog.onHelpReceived(message); + mCurrentDialog.show(mWindowManager, skipAnimation); } - private void handleBiometricError(String error) { - if (DEBUG) Log.d(TAG, "handleBiometricError: " + error); - if (!mDialogShowing) { - if (DEBUG) Log.d(TAG, "Dialog already dismissed"); - return; - } - mCurrentDialog.onErrorReceived(error); - } - - private void handleHideDialog(boolean userCanceled) { - if (DEBUG) Log.d(TAG, "handleHideDialog, userCanceled: " + userCanceled); - if (!mDialogShowing) { - // This can happen if there's a race and we get called from both - // onAuthenticated and onError, etc. - Log.w(TAG, "Dialog already dismissed, userCanceled: " + userCanceled); - return; - } - if (userCanceled) { - try { - mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException when hiding dialog", e); - } + private void onDialogDismissed(@DismissedReason int reason) { + if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason); + if (mCurrentDialog == null) { + Log.w(TAG, "Dialog already dismissed"); } mReceiver = null; - mDialogShowing = false; - mCurrentDialog.startDismiss(); - } - - private void handleButtonNegative() { - if (mReceiver == null) { - Log.e(TAG, "Receiver is null"); - return; - } - try { - mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE); - } catch (RemoteException e) { - Log.e(TAG, "Remote exception when handling negative button", e); - } - handleHideDialog(false /* userCanceled */); - } - - private void handleButtonPositive() { - if (mReceiver == null) { - Log.e(TAG, "Receiver is null"); - return; - } - try { - mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_POSITIVE); - } catch (RemoteException e) { - Log.e(TAG, "Remote exception when handling positive button", e); - } - handleHideDialog(false /* userCanceled */); - } - - private void handleUserCanceled() { - handleHideDialog(true /* userCanceled */); - } - - private void handleTryAgainPressed() { - try { - mReceiver.onTryAgainPressed(); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException when handling try again", e); - } + mCurrentDialog = null; } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - final boolean wasShowing = mDialogShowing; // Save the state of the current dialog (buttons showing, etc) - final Bundle savedState = new Bundle(); if (mCurrentDialog != null) { + final Bundle savedState = new Bundle(); mCurrentDialog.onSaveState(savedState); - } + mCurrentDialog.dismissWithoutCallback(false /* animate */); + mCurrentDialog = null; - if (mDialogShowing) { - mCurrentDialog.forceRemove(); - mDialogShowing = false; + showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); } + } - if (wasShowing) { - handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); - } + protected BiometricDialog buildDialog(Bundle biometricPromptBundle, + boolean requireConfirmation, int userId, int type, String opPackageName) { + return new BiometricDialogView.Builder(mContext) + .setCallback(this) + .setBiometricPromptBundle(biometricPromptBundle) + .setRequireConfirmation(requireConfirmation) + .setUserId(userId) + .setOpPackageName(opPackageName) + .build(type); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java index 24fd22e2ee80..b65d1e823a9b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java @@ -16,36 +16,38 @@ package com.android.systemui.biometrics; +import android.annotation.IntDef; + /** * Callback interface for dialog views. These should be implemented by the controller (e.g. * FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView). */ public interface DialogViewCallback { - /** - * Invoked when the user cancels authentication by tapping outside the prompt, etc. The dialog - * should be dismissed. - */ - void onUserCanceled(); - /** - * Invoked when an error is shown. The dialog should be dismissed after a set amount of time. - */ - void onErrorShown(); + int DISMISSED_USER_CANCELED = 1; + int DISMISSED_BUTTON_NEGATIVE = 2; + int DISMISSED_BUTTON_POSITIVE = 3; - /** - * Invoked when the negative button is pressed. The client should be notified and the dialog - * should be dismissed. - */ - void onNegativePressed(); + int DISMISSED_AUTHENTICATED = 4; + int DISMISSED_ERROR = 5; + int DISMISSED_BY_SYSTEM_SERVER = 6; + + @IntDef({DISMISSED_USER_CANCELED, + DISMISSED_BUTTON_NEGATIVE, + DISMISSED_BUTTON_POSITIVE, + DISMISSED_AUTHENTICATED, + DISMISSED_ERROR, + DISMISSED_BY_SYSTEM_SERVER}) + @interface DismissedReason {} /** - * Invoked when the positive button is pressed. The client should be notified and the dialog - * should be dismissed. + * Invoked when the dialog is dismissed + * @param reason */ - void onPositivePressed(); + void onDismissed(@DismissedReason int reason); /** - * Invoked when the "try again" button is pressed. + * Invoked when the "try again" button is clicked */ void onTryAgainPressed(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java index ce67577ea483..2b4dde55ef72 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.biometrics; +package com.android.systemui.biometrics.ui; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; @@ -23,6 +23,7 @@ import android.content.Context; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; import android.os.Binder; import android.os.Bundle; @@ -46,25 +47,30 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.biometrics.BiometricDialog; +import com.android.systemui.biometrics.DialogViewCallback; +import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.util.leak.RotationUtils; /** * Abstract base class. Shows a dialog for BiometricPrompt. */ -public abstract class BiometricDialogView extends LinearLayout { +public abstract class BiometricDialogView extends LinearLayout implements BiometricDialog { private static final String TAG = "BiometricDialogView"; - private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility"; - private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility"; - private static final String KEY_CONFIRM_ENABLED = "key_confirm_enabled"; - private static final String KEY_STATE = "key_state"; - private static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility"; - private static final String KEY_ERROR_TEXT_STRING = "key_error_text_string"; - private static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary"; - private static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color"; + public static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility"; + public static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility"; + public static final String KEY_CONFIRM_ENABLED = "key_confirm_enabled"; + public static final String KEY_STATE = "key_state"; + public static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility"; + public static final String KEY_ERROR_TEXT_STRING = "key_error_text_string"; + public static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary"; + public static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color"; private static final int ANIMATION_DURATION_SHOW = 250; // ms private static final int ANIMATION_DURATION_AWAY = 350; // ms @@ -77,6 +83,8 @@ public abstract class BiometricDialogView extends LinearLayout { protected static final int STATE_PENDING_CONFIRMATION = 3; protected static final int STATE_AUTHENTICATED = 4; + @VisibleForTesting + final WakefulnessLifecycle mWakefulnessLifecycle; private final AccessibilityManager mAccessibilityManager; private final IBinder mWindowToken = new Binder(); private final Interpolator mLinearOutSlowIn; @@ -90,22 +98,30 @@ public abstract class BiometricDialogView extends LinearLayout { protected final ViewGroup mLayout; protected final LinearLayout mDialog; - protected final TextView mTitleText; - protected final TextView mSubtitleText; - protected final TextView mDescriptionText; - protected final ImageView mBiometricIcon; - protected final TextView mErrorText; - protected final Button mPositiveButton; - protected final Button mNegativeButton; - protected final Button mTryAgainButton; + @VisibleForTesting + final TextView mTitleText; + @VisibleForTesting + final TextView mSubtitleText; + @VisibleForTesting + final TextView mDescriptionText; + @VisibleForTesting + final ImageView mBiometricIcon; + @VisibleForTesting + final TextView mErrorText; + @VisibleForTesting + final Button mPositiveButton; + @VisibleForTesting + final Button mNegativeButton; + @VisibleForTesting + final Button mTryAgainButton; protected final int mTextColor; private Bundle mBundle; private Bundle mRestoredState; + private String mOpPackageName; private int mState = STATE_IDLE; - private boolean mAnimatingAway; private boolean mWasForceRemoved; private boolean mSkipIntro; protected boolean mRequireConfirmation; @@ -141,6 +157,15 @@ public abstract class BiometricDialogView extends LinearLayout { } }; + @VisibleForTesting + final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + @Override + public void onStartedGoingToSleep() { + animateAway(DialogViewCallback.DISMISSED_USER_CANCELED); + } + }; + protected Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -155,8 +180,80 @@ public abstract class BiometricDialogView extends LinearLayout { } }; - public BiometricDialogView(Context context, DialogViewCallback callback) { + /** + * Builds the dialog with specified parameters. + */ + public static class Builder { + public static final int TYPE_FINGERPRINT = BiometricAuthenticator.TYPE_FINGERPRINT; + public static final int TYPE_FACE = BiometricAuthenticator.TYPE_FACE; + + private Context mContext; + private DialogViewCallback mCallback; + private Bundle mBundle; + private boolean mRequireConfirmation; + private int mUserId; + private String mOpPackageName; + + public Builder(Context context) { + mContext = context; + } + + public Builder setCallback(DialogViewCallback callback) { + mCallback = callback; + return this; + } + + public Builder setBiometricPromptBundle(Bundle bundle) { + mBundle = bundle; + return this; + } + + public Builder setRequireConfirmation(boolean requireConfirmation) { + mRequireConfirmation = requireConfirmation; + return this; + } + + public Builder setUserId(int userId) { + mUserId = userId; + return this; + } + + public Builder setOpPackageName(String opPackageName) { + mOpPackageName = opPackageName; + return this; + } + + public BiometricDialogView build(int type) { + return build(type, new Injector()); + } + + public BiometricDialogView build(int type, Injector injector) { + BiometricDialogView dialog; + if (type == TYPE_FINGERPRINT) { + dialog = new FingerprintDialogView(mContext, mCallback, injector); + } else if (type == TYPE_FACE) { + dialog = new FaceDialogView(mContext, mCallback, injector); + } else { + return null; + } + dialog.setBundle(mBundle); + dialog.setRequireConfirmation(mRequireConfirmation); + dialog.setUserId(mUserId); + dialog.setOpPackageName(mOpPackageName); + return dialog; + } + } + + public static class Injector { + public WakefulnessLifecycle getWakefulnessLifecycle() { + return Dependency.get(WakefulnessLifecycle.class); + } + } + + protected BiometricDialogView(Context context, DialogViewCallback callback, Injector injector) { super(context); + mWakefulnessLifecycle = injector.getWakefulnessLifecycle(); + mCallback = callback; mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); @@ -178,19 +275,13 @@ public abstract class BiometricDialogView extends LinearLayout { addView(mLayout); mLayout.setOnKeyListener(new View.OnKeyListener() { - boolean downPressed = false; @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (keyCode != KeyEvent.KEYCODE_BACK) { return false; } - if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) { - downPressed = true; - } else if (event.getAction() == KeyEvent.ACTION_DOWN) { - downPressed = false; - } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) { - downPressed = false; - mCallback.onUserCanceled(); + if (event.getAction() == KeyEvent.ACTION_UP) { + animateAway(DialogViewCallback.DISMISSED_USER_CANCELED); } return true; } @@ -219,16 +310,16 @@ public abstract class BiometricDialogView extends LinearLayout { mNegativeButton.setOnClickListener((View v) -> { if (mState == STATE_PENDING_CONFIRMATION || mState == STATE_AUTHENTICATED) { - mCallback.onUserCanceled(); + animateAway(DialogViewCallback.DISMISSED_USER_CANCELED); } else { - mCallback.onNegativePressed(); + animateAway(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE); } }); mPositiveButton.setOnClickListener((View v) -> { updateState(STATE_AUTHENTICATED); mHandler.postDelayed(() -> { - mCallback.onPositivePressed(); + animateAway(DialogViewCallback.DISMISSED_BUTTON_POSITIVE); }, getDelayAfterAuthenticatedDurationMs()); }); @@ -248,21 +339,12 @@ public abstract class BiometricDialogView extends LinearLayout { mLayout.requestFocus(); } - public void onSaveState(Bundle bundle) { - bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility()); - bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility()); - bundle.putBoolean(KEY_CONFIRM_ENABLED, mPositiveButton.isEnabled()); - bundle.putInt(KEY_STATE, mState); - bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility()); - bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText()); - bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE)); - bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor()); - } - @Override public void onAttachedToWindow() { super.onAttachedToWindow(); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + final ImageView backgroundView = mLayout.findViewById(R.id.background); if (mUserManager.isManagedProfile(mUserId)) { @@ -278,6 +360,7 @@ public abstract class BiometricDialogView extends LinearLayout { } mNegativeButton.setVisibility(View.VISIBLE); + mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); if (RotationUtils.getRotation(mContext) != RotationUtils.ROTATION_NONE) { mDialog.getLayoutParams().width = (int) mDialogWidth; @@ -285,7 +368,6 @@ public abstract class BiometricDialogView extends LinearLayout { if (mRestoredState == null) { updateState(STATE_AUTHENTICATING); - mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); final int hint = getHintStringResourceId(); if (hint != 0) { mErrorText.setText(hint); @@ -346,34 +428,49 @@ public abstract class BiometricDialogView extends LinearLayout { mSkipIntro = false; } + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); + } + private void setDismissesDialog(View v) { v.setClickable(true); v.setOnClickListener(v1 -> { if (mState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) { - mCallback.onUserCanceled(); + animateAway(DialogViewCallback.DISMISSED_USER_CANCELED); } }); } - public void startDismiss() { + private void animateAway(@DialogViewCallback.DismissedReason int reason) { + animateAway(true /* sendReason */, reason); + } + + /** + * Animate the dialog away + * @param reason one of the {@link DialogViewCallback} codes + */ + private void animateAway(boolean sendReason, @DialogViewCallback.DismissedReason int reason) { if (!mCompletedAnimatingIn) { Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn"); mPendingDismissDialog = true; return; } - mAnimatingAway = true; - // This is where final cleanup should occur. final Runnable endActionRunnable = new Runnable() { @Override public void run() { mWindowManager.removeView(BiometricDialogView.this); - mAnimatingAway = false; // Set the icons / text back to normal state handleResetMessage(); showTryAgainButton(false /* show */); updateState(STATE_IDLE); + if (sendReason) { + mCallback.onDismissed(reason); + } } }; @@ -398,47 +495,30 @@ public abstract class BiometricDialogView extends LinearLayout { } /** - * Force remove the window, cancelling any animation that's happening. This should only be - * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method - * will cause the dialog to show without an animation the next time it's attached. - */ - public void forceRemove() { - mLayout.animate().cancel(); - mDialog.animate().cancel(); - mWindowManager.removeView(BiometricDialogView.this); - mAnimatingAway = false; - mWasForceRemoved = true; - } - - /** * Skip the intro animation */ - public void setSkipIntro(boolean skip) { + private void setSkipIntro(boolean skip) { mSkipIntro = skip; } - public boolean isAnimatingAway() { - return mAnimatingAway; - } - - public void setBundle(Bundle bundle) { + private void setBundle(Bundle bundle) { mBundle = bundle; } - public void setRequireConfirmation(boolean requireConfirmation) { + private void setRequireConfirmation(boolean requireConfirmation) { mRequireConfirmation = requireConfirmation; } - public boolean requiresConfirmation() { + protected boolean requiresConfirmation() { return mRequireConfirmation; } - public void setUserId(int userId) { + private void setUserId(int userId) { mUserId = userId; } - public ViewGroup getLayout() { - return mLayout; + private void setOpPackageName(String opPackageName) { + mOpPackageName = opPackageName; } // Shows an error/help message @@ -452,17 +532,63 @@ public abstract class BiometricDialogView extends LinearLayout { BiometricPrompt.HIDE_DIALOG_DELAY); } + @Override + public void show(WindowManager wm, boolean skipIntroAnimation) { + setSkipIntro(skipIntroAnimation); + wm.addView(this, getLayoutParams(mWindowToken)); + } + /** - * Transient help message (acquire) is received, dialog stays showing. Sensor stays in - * "authenticating" state. - * @param message + * Force remove the window, cancelling any animation that's happening. This should only be + * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method + * will cause the dialog to show without an animation the next time it's attached. */ - public void onHelpReceived(String message) { + @Override + public void dismissWithoutCallback(boolean animate) { + if (animate) { + animateAway(false /* sendReason */, 0 /* reason */); + } else { + mLayout.animate().cancel(); + mDialog.animate().cancel(); + mWindowManager.removeView(BiometricDialogView.this); + mWasForceRemoved = true; + } + } + + @Override + public void dismissFromSystemServer() { + animateAway(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER); + } + + @Override + public void onAuthenticationSucceeded() { + announceForAccessibility(getResources().getText(getAuthenticatedAccessibilityResourceId())); + + if (requiresConfirmation()) { + updateState(STATE_PENDING_CONFIRMATION); + } else { + mHandler.postDelayed(() -> { + animateAway(DialogViewCallback.DISMISSED_AUTHENTICATED); + }, getDelayAfterAuthenticatedDurationMs()); + + updateState(STATE_AUTHENTICATED); + } + } + + + @Override + public void onAuthenticationFailed(String message) { updateState(STATE_ERROR); showTemporaryMessage(message); } - public void onAuthenticationFailed(String message) { + /** + * Transient help message (acquire) is received, dialog stays showing. Sensor stays in + * "authenticating" state. + * @param message + */ + @Override + public void onHelp(String message) { updateState(STATE_ERROR); showTemporaryMessage(message); } @@ -471,14 +597,61 @@ public abstract class BiometricDialogView extends LinearLayout { * Hard error is received, dialog will be dismissed soon. * @param error */ - public void onErrorReceived(String error) { + @Override + public void onError(String error) { updateState(STATE_ERROR); showTemporaryMessage(error); showTryAgainButton(false /* show */); - mCallback.onErrorShown(); // TODO: Split between fp and face + + mHandler.postDelayed(() -> { + animateAway(DialogViewCallback.DISMISSED_ERROR); + }, BiometricPrompt.HIDE_DIALOG_DELAY); + } + + + @Override + public void onSaveState(Bundle bundle) { + bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility()); + bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility()); + bundle.putBoolean(KEY_CONFIRM_ENABLED, mPositiveButton.isEnabled()); + bundle.putInt(KEY_STATE, mState); + bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility()); + bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText()); + bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE)); + bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor()); + } + + @Override + public void restoreState(Bundle bundle) { + mRestoredState = bundle; + final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY); + mTryAgainButton.setVisibility(tryAgainVisibility); + final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY); + mPositiveButton.setVisibility(confirmVisibility); + final boolean confirmEnabled = bundle.getBoolean(KEY_CONFIRM_ENABLED); + mPositiveButton.setEnabled(confirmEnabled); + mState = bundle.getInt(KEY_STATE); + mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING)); + mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING)); + final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY); + mErrorText.setVisibility(errorTextVisibility); + if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE + || confirmVisibility == View.INVISIBLE) { + announceAccessibilityEvent(); + } + mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR)); + if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE), + BiometricPrompt.HIDE_DIALOG_DELAY); + } } - public void updateState(int newState) { + @Override + public String getOpPackageName() { + return mOpPackageName; + } + + protected void updateState(int newState) { if (newState == STATE_PENDING_CONFIRMATION) { mHandler.removeMessages(MSG_RESET_MESSAGE); mErrorText.setTextColor(mTextColor); @@ -505,48 +678,24 @@ public abstract class BiometricDialogView extends LinearLayout { mState = newState; } - public void showTryAgainButton(boolean show) { + protected void showTryAgainButton(boolean show) { } - public void onDialogAnimatedIn() { + protected void onDialogAnimatedIn() { mCompletedAnimatingIn = true; if (mPendingDismissDialog) { Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now"); - startDismiss(); + animateAway(false /* sendReason */, 0); mPendingDismissDialog = false; } } - public void restoreState(Bundle bundle) { - mRestoredState = bundle; - final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY); - mTryAgainButton.setVisibility(tryAgainVisibility); - final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY); - mPositiveButton.setVisibility(confirmVisibility); - final boolean confirmEnabled = bundle.getBoolean(KEY_CONFIRM_ENABLED); - mPositiveButton.setEnabled(confirmEnabled); - mState = bundle.getInt(KEY_STATE); - mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING)); - mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING)); - final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY); - mErrorText.setVisibility(errorTextVisibility); - if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE - || confirmVisibility == View.INVISIBLE) { - announceAccessibilityEvent(); - } - mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR)); - if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE), - BiometricPrompt.HIDE_DIALOG_DELAY); - } - } - - protected int getState() { - return mState; - } - - public WindowManager.LayoutParams getLayoutParams() { + /** + * @param windowToken token for the window + * @return + */ + public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, @@ -555,7 +704,7 @@ public abstract class BiometricDialogView extends LinearLayout { PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("BiometricDialogView"); - lp.token = mWindowToken; + lp.token = windowToken; return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java index ae6cb5ce23d3..bd8714812ab4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.biometrics; +package com.android.systemui.biometrics.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -33,7 +33,9 @@ import android.util.Log; import android.view.View; import android.view.ViewOutlineProvider; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.biometrics.DialogViewCallback; /** * This class loads the view for the system-provided dialog. The view consists of: @@ -52,7 +54,8 @@ public class FaceDialogView extends BiometricDialogView { private static final int TEXT_ANIMATE_DISTANCE = 32; // dp private static final int SIZE_UNKNOWN = 0; - private static final int SIZE_SMALL = 1; + @VisibleForTesting + static final int SIZE_SMALL = 1; private static final int SIZE_GROWING = 2; private static final int SIZE_BIG = 3; @@ -152,13 +155,13 @@ public class FaceDialogView extends BiometricDialogView { announceAccessibilityEvent(); }; - public FaceDialogView(Context context, - DialogViewCallback callback) { - super(context, callback); + protected FaceDialogView(Context context, DialogViewCallback callback, Injector injector) { + super(context, callback, injector); mIconController = new IconController(); } - private void updateSize(int newSize) { + @VisibleForTesting + void updateSize(int newSize) { final float padding = dpToPixels(IMPLICIT_Y_PADDING); final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding; @@ -339,8 +342,8 @@ public class FaceDialogView extends BiometricDialogView { } @Override - public void onErrorReceived(String error) { - super.onErrorReceived(error); + public void onError(String error) { + super.onError(error); // All error messages will cause the dialog to go from small -> big. Error messages // are messages such as lockout, auth failed, etc. if (mSize == SIZE_SMALL) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java index 183933eb395c..e597080f89fd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.biometrics; +package com.android.systemui.biometrics.ui; import android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; @@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable; import android.util.Log; import com.android.systemui.R; +import com.android.systemui.biometrics.DialogViewCallback; /** * This class loads the view for the system-provided dialog. The view consists of: @@ -32,9 +33,9 @@ public class FingerprintDialogView extends BiometricDialogView { private static final String TAG = "FingerprintDialogView"; - public FingerprintDialogView(Context context, - DialogViewCallback callback) { - super(context, callback); + protected FingerprintDialogView(Context context, DialogViewCallback callback, + Injector injector) { + super(context, callback, injector); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index fa0fe136b8eb..134d4b87a159 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -271,7 +271,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void onRotationProposal(int rotation, boolean isValid) { } default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId) { } + int type, boolean requireConfirmation, int userId, String opPackageName) { } default void onBiometricAuthenticated(boolean authenticated, String failureReason) { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } @@ -741,7 +741,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< @Override public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId) { + int type, boolean requireConfirmation, int userId, String opPackageName) { synchronized (mLock) { SomeArgs args = SomeArgs.obtain(); args.arg1 = bundle; @@ -749,6 +749,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< args.argi1 = type; args.arg3 = requireConfirmation; args.argi2 = userId; + args.arg4 = opPackageName; mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args) .sendToTarget(); } @@ -1036,7 +1037,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< (IBiometricServiceReceiverInternal) someArgs.arg2, someArgs.argi1 /* type */, (boolean) someArgs.arg3 /* requireConfirmation */, - someArgs.argi2 /* userId */); + someArgs.argi2 /* userId */, + (String) someArgs.arg4 /* opPackageName */); } someArgs.recycle(); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index e75365e66f81..7acf4fd27ced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -537,8 +537,14 @@ public class MobileSignalController extends SignalController< return mConfig.nr5GIconMap.get(Config.NR_CONNECTED); } } else if (nrState == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED) { - if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED)) { - return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED); + if (mCurrentState.activityDormant) { + if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED_RRC_IDLE)) { + return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED_RRC_IDLE); + } + } else { + if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED_RRC_CON)) { + return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED_RRC_CON); + } } } else if (nrState == NetworkRegistrationInfo.NR_STATE_RESTRICTED) { if (mConfig.nr5GIconMap.containsKey(Config.NR_RESTRICTED)) { @@ -559,6 +565,8 @@ public class MobileSignalController extends SignalController< || activity == TelephonyManager.DATA_ACTIVITY_IN; mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT || activity == TelephonyManager.DATA_ACTIVITY_OUT; + mCurrentState.activityDormant = activity == TelephonyManager.DATA_ACTIVITY_DORMANT; + notifyListenersIfNecessary(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 04f96a46bbf7..292571e1d9b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -1112,8 +1112,9 @@ public class NetworkControllerImpl extends BroadcastReceiver static class Config { static final int NR_CONNECTED_MMWAVE = 1; static final int NR_CONNECTED = 2; - static final int NR_NOT_RESTRICTED = 3; - static final int NR_RESTRICTED = 4; + static final int NR_NOT_RESTRICTED_RRC_IDLE = 3; + static final int NR_NOT_RESTRICTED_RRC_CON = 4; + static final int NR_RESTRICTED = 5; Map<Integer, MobileIconGroup> nr5GIconMap = new HashMap<>(); @@ -1133,10 +1134,11 @@ public class NetworkControllerImpl extends BroadcastReceiver */ private static final Map<String, Integer> NR_STATUS_STRING_TO_INDEX; static { - NR_STATUS_STRING_TO_INDEX = new HashMap<>(4); + NR_STATUS_STRING_TO_INDEX = new HashMap<>(5); NR_STATUS_STRING_TO_INDEX.put("connected_mmwave", NR_CONNECTED_MMWAVE); NR_STATUS_STRING_TO_INDEX.put("connected", NR_CONNECTED); - NR_STATUS_STRING_TO_INDEX.put("not_restricted", NR_NOT_RESTRICTED); + NR_STATUS_STRING_TO_INDEX.put("not_restricted_rrc_idle", NR_NOT_RESTRICTED_RRC_IDLE); + NR_STATUS_STRING_TO_INDEX.put("not_restricted_rrc_con", NR_NOT_RESTRICTED_RRC_CON); NR_STATUS_STRING_TO_INDEX.put("restricted", NR_RESTRICTED); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java index 9ec30d43ac75..abe3f2c18891 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java @@ -258,6 +258,7 @@ public abstract class SignalController<T extends SignalController.State, boolean enabled; boolean activityIn; boolean activityOut; + public boolean activityDormant; int level; IconGroup iconGroup; int inetCondition; @@ -274,6 +275,7 @@ public abstract class SignalController<T extends SignalController.State, inetCondition = state.inetCondition; activityIn = state.activityIn; activityOut = state.activityOut; + activityDormant = state.activityDormant; rssi = state.rssi; time = state.time; } @@ -297,6 +299,7 @@ public abstract class SignalController<T extends SignalController.State, .append("iconGroup=").append(iconGroup).append(',') .append("activityIn=").append(activityIn).append(',') .append("activityOut=").append(activityOut).append(',') + .append("activityDormant=").append(activityDormant).append(',') .append("rssi=").append(rssi).append(',') .append("lastModified=").append(DateFormat.format("MM-dd HH:mm:ss", time)); } @@ -314,6 +317,7 @@ public abstract class SignalController<T extends SignalController.State, && other.iconGroup == iconGroup && other.activityIn == activityIn && other.activityOut == activityOut + && other.activityDormant == activityDormant && other.rssi == rssi; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java new file mode 100644 index 000000000000..8f2f8b1c0e63 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; +import static junit.framework.TestCase.assertNotNull; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.IActivityTaskManager; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; +import android.os.Bundle; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper.RunWithLooper; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.StatusBar; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +@SmallTest +public class BiometricDialogImplTest extends SysuiTestCase { + + @Mock + private PackageManager mPackageManager; + @Mock + private IBiometricServiceReceiverInternal mReceiver; + @Mock + private BiometricDialog mDialog1; + @Mock + private BiometricDialog mDialog2; + + private TestableBiometricDialogImpl mBiometricDialogImpl; + + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + TestableContext context = spy(mContext); + + mContext.putComponent(StatusBar.class, mock(StatusBar.class)); + mContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); + + when(context.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) + .thenReturn(true); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) + .thenReturn(true); + + when(mDialog1.getOpPackageName()).thenReturn("Dialog1"); + when(mDialog2.getOpPackageName()).thenReturn("Dialog2"); + + mBiometricDialogImpl = new TestableBiometricDialogImpl(new MockInjector()); + mBiometricDialogImpl.mContext = context; + mBiometricDialogImpl.mComponents = mContext.getComponents(); + + mBiometricDialogImpl.start(); + } + + // Callback tests + + @Test + public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_USER_CANCELED); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + } + + @Test + public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE); + } + + @Test + public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_POSITIVE); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRMED); + } + + @Test + public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_AUTHENTICATED); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED); + } + + @Test + public void testSendsReasonError_whenDismissedByError() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_ERROR); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR); + } + + @Test + public void testSendsReasonDismissedBySystemServer_whenDismissedByServer() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER); + verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED); + } + + // Statusbar tests + + @Test + public void testShowInvoked_whenSystemRequested() + throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + verify(mDialog1).show(any(), eq(false) /* skipIntro */); + } + + @Test + public void testOnAuthenticationSucceededInvoked_whenSystemRequested() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.onBiometricAuthenticated(true, null /* failureReason */); + verify(mDialog1).onAuthenticationSucceeded(); + } + + @Test + public void testOnAuthenticationFailedInvoked_whenSystemRequested() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + final String failureReason = "failure reason"; + mBiometricDialogImpl.onBiometricAuthenticated(false, failureReason); + + ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); + verify(mDialog1).onAuthenticationFailed(captor.capture()); + + assertEquals(captor.getValue(), failureReason); + } + + @Test + public void testOnHelpInvoked_whenSystemRequested() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + final String helpMessage = "help"; + mBiometricDialogImpl.onBiometricHelp(helpMessage); + + ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); + verify(mDialog1).onHelp(captor.capture()); + + assertEquals(captor.getValue(), helpMessage); + } + + @Test + public void testOnErrorInvoked_whenSystemRequested() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + final String errMessage = "error message"; + mBiometricDialogImpl.onBiometricError(errMessage); + + ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); + verify(mDialog1).onError(captor.capture()); + + assertEquals(captor.getValue(), errMessage); + } + + @Test + public void testDismissWithoutCallbackInvoked_whenSystemRequested() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.hideBiometricDialog(); + verify(mDialog1).dismissFromSystemServer(); + } + + @Test + public void testClientNotified_whenDismissedBySystemServer() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + mBiometricDialogImpl.hideBiometricDialog(); + verify(mDialog1).dismissFromSystemServer(); + + assertNotNull(mBiometricDialogImpl.mCurrentDialog); + assertNotNull(mBiometricDialogImpl.mReceiver); + } + + // Corner case tests + + @Test + public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + verify(mDialog1).show(any(), eq(false) /* skipIntro */); + + showDialog(BiometricPrompt.TYPE_FACE); + + // First dialog should be dismissed without animation + verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */); + + // Second dialog should be shown without animation + verify(mDialog2).show(any(), eq(true)) /* skipIntro */; + } + + @Test + public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + verify(mDialog1).show(any(), eq(false) /* skipIntro */); + + mBiometricDialogImpl.onConfigurationChanged(new Configuration()); + + ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class); + verify(mDialog1).onSaveState(captor.capture()); + + // Old dialog doesn't animate + verify(mDialog1).dismissWithoutCallback(eq(false /* animate */)); + + // Saved state is restored into new dialog + ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class); + verify(mDialog2).restoreState(captor2.capture()); + + // Dialog for new configuration skips intro + verify(mDialog2).show(any(), eq(true) /* skipIntro */); + + // TODO: This should check all values we want to save/restore + assertEquals(captor.getValue(), captor2.getValue()); + } + + @Test + public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception { + showDialog(BiometricPrompt.TYPE_FACE); + + List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>(); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + taskInfo.topActivity = mock(ComponentName.class); + when(taskInfo.topActivity.getPackageName()).thenReturn("other_package"); + tasks.add(taskInfo); + when(mBiometricDialogImpl.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks); + + mBiometricDialogImpl.mTaskStackListener.onTaskStackChanged(); + waitForIdleSync(); + + assertNull(mBiometricDialogImpl.mCurrentDialog); + assertNull(mBiometricDialogImpl.mReceiver); + verify(mDialog1).dismissWithoutCallback(true /* animate */); + verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL)); + } + + // Helpers + + private void showDialog(int type) { + mBiometricDialogImpl.showBiometricDialog(createTestDialogBundle(), + mReceiver /* receiver */, + type, + true /* requireConfirmation */, + 0 /* userId */, + "testPackage"); + } + + private Bundle createTestDialogBundle() { + Bundle bundle = new Bundle(); + + bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title"); + bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, "Subtitle"); + bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, "Description"); + bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative Button"); + + // RequireConfirmation is a hint to BiometricService. This can be forced to be required + // by user settings, and should be tested in BiometricService. + bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true); + + return bundle; + } + + private final class TestableBiometricDialogImpl extends BiometricDialogImpl { + private int mBuildCount = 0; + + public TestableBiometricDialogImpl(Injector injector) { + super(injector); + } + + @Override + protected BiometricDialog buildDialog(Bundle biometricPromptBundle, + boolean requireConfirmation, int userId, int type, String opPackageName) { + BiometricDialog dialog; + if (mBuildCount == 0) { + dialog = mDialog1; + } else if (mBuildCount == 1) { + dialog = mDialog2; + } else { + dialog = null; + } + mBuildCount++; + return dialog; + } + } + + private final class MockInjector extends BiometricDialogImpl.Injector { + @Override + IActivityTaskManager getActivityTaskManager() { + return mock(IActivityTaskManager.class); + } + } +} + diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java new file mode 100644 index 000000000000..bbdd837bb446 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.spy; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.hardware.biometrics.BiometricPrompt; +import android.os.Bundle; +import android.os.UserManager; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper.RunWithLooper; +import android.view.View; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.DialogViewCallback; +import com.android.systemui.keyguard.WakefulnessLifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +@SmallTest +public class BiometricDialogViewTest extends SysuiTestCase { + + FaceDialogView mFaceDialogView; + + private static final String TITLE = "Title"; + private static final String SUBTITLE = "Subtitle"; + private static final String DESCRIPTION = "Description"; + private static final String NEGATIVE_BUTTON = "Negative Button"; + + private static final String TEST_HELP = "Help"; + + TestableContext mTestableContext; + @Mock + private DialogViewCallback mCallback; + @Mock + private UserManager mUserManager; + @Mock + private DevicePolicyManager mDpm; + + private static class Injector extends BiometricDialogView.Injector { + @Override + public WakefulnessLifecycle getWakefulnessLifecycle() { + final WakefulnessLifecycle lifecycle = new WakefulnessLifecycle(); + lifecycle.dispatchFinishedWakingUp(); + return lifecycle; + } + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mTestableContext = spy(mContext); + mTestableContext.addMockSystemService(UserManager.class, mUserManager); + mTestableContext.addMockSystemService(DevicePolicyManager.class, mDpm); + } + + @Test + public void testContentStates_confirmationRequired_authenticated() { + mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback, + true /* requireConfirmation */); + mFaceDialogView.onAttachedToWindow(); + + // When starting authentication + assertEquals(View.VISIBLE, mFaceDialogView.mTitleText.getVisibility()); + assertEquals(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility()); + assertEquals(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility()); + assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility()); + assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility()); + assertEquals(View.VISIBLE, mFaceDialogView.mNegativeButton.getVisibility()); + assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility()); + + // Contents are as expected + assertTrue(TITLE.contentEquals(mFaceDialogView.mTitleText.getText())); + assertTrue(SUBTITLE.contentEquals(mFaceDialogView.mSubtitleText.getText())); + assertTrue(DESCRIPTION.contentEquals(mFaceDialogView.mDescriptionText.getText())); + assertTrue(mFaceDialogView.mPositiveButton.getText().toString() + .contentEquals(mContext.getString(R.string.biometric_dialog_confirm))); + assertTrue(NEGATIVE_BUTTON.contentEquals(mFaceDialogView.mNegativeButton.getText())); + assertTrue(mFaceDialogView.mTryAgainButton.getText().toString() + .contentEquals(mContext.getString(R.string.biometric_dialog_try_again))); + + // When help message is received + mFaceDialogView.onHelp(TEST_HELP); + assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE); + assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText())); + + // When authenticated, confirm button comes out + mFaceDialogView.onAuthenticationSucceeded(); + assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility()); + assertEquals(true, mFaceDialogView.mPositiveButton.isEnabled()); + } + + @Test + public void testContentStates_confirmationNotRequired_authenticated() { + mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback, + false /* requireConfirmation */); + mFaceDialogView.onAttachedToWindow(); + mFaceDialogView.updateSize(FaceDialogView.SIZE_SMALL); + + assertEquals(View.INVISIBLE, mFaceDialogView.mTitleText.getVisibility()); + assertNotSame(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility()); + assertNotSame(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility()); + assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility()); + assertEquals(View.GONE, mFaceDialogView.mPositiveButton.getVisibility()); + assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility()); + assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility()); + } + + @Test + public void testContentStates_confirmationNotRequired_help() { + mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback, + false /* requireConfirmation */); + mFaceDialogView.onAttachedToWindow(); + + mFaceDialogView.onHelp(TEST_HELP); + assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE); + assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText())); + } + + @Test + public void testBack_sendsUserCanceled() { + // TODO: Need robolectric framework to wait for handler to complete + } + + @Test + public void testScreenOff_sendsUserCanceled() { + // TODO: Need robolectric framework to wait for handler to complete + } + + @Test + public void testRestoreState_contentStatesCorrect() { + mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback, + false /* requireConfirmation */); + mFaceDialogView.onAttachedToWindow(); + mFaceDialogView.onAuthenticationFailed(TEST_HELP); + + final Bundle bundle = new Bundle(); + mFaceDialogView.onSaveState(bundle); + + mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback, + false /* requireConfirmation */); + mFaceDialogView.restoreState(bundle); + mFaceDialogView.onAttachedToWindow(); + + assertEquals(View.VISIBLE, mFaceDialogView.mTryAgainButton.getVisibility()); + } + + private FaceDialogView buildFaceDialogView(Context context, DialogViewCallback callback, + boolean requireConfirmation) { + return (FaceDialogView) new BiometricDialogView.Builder(context) + .setCallback(callback) + .setBiometricPromptBundle(createTestDialogBundle()) + .setRequireConfirmation(requireConfirmation) + .setUserId(0) + .setOpPackageName("test_package") + .build(BiometricDialogView.Builder.TYPE_FACE, new Injector()); + } + + private Bundle createTestDialogBundle() { + Bundle bundle = new Bundle(); + + bundle.putCharSequence(BiometricPrompt.KEY_TITLE, TITLE); + bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, SUBTITLE); + bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, DESCRIPTION); + bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, NEGATIVE_BUTTON); + + // RequireConfirmation is a hint to BiometricService. This can be forced to be required + // by user settings, and should be tested in BiometricService. + bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true); + + return bundle; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 1e1f21568377..b252a0d95f94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -365,4 +365,45 @@ public class CommandQueueTest extends SysuiTestCase { waitForIdleSync(); verify(mCallbacks).onRecentsAnimationStateChanged(eq(true)); } + + @Test + public void testShowBiometricDialog() { + Bundle bundle = new Bundle(); + String packageName = "test"; + mCommandQueue.showBiometricDialog(bundle, null /* receiver */, 1, true, 3, packageName); + waitForIdleSync(); + verify(mCallbacks).showBiometricDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3), + eq(packageName)); + } + + @Test + public void testOnBiometricAuthenticated() { + String failureReason = "test_failure_reason"; + mCommandQueue.onBiometricAuthenticated(true /* authenticated */, failureReason); + waitForIdleSync(); + verify(mCallbacks).onBiometricAuthenticated(eq(true), eq(failureReason)); + } + + @Test + public void testOnBiometricHelp() { + String helpMessage = "test_help_message"; + mCommandQueue.onBiometricHelp(helpMessage); + waitForIdleSync(); + verify(mCallbacks).onBiometricHelp(eq(helpMessage)); + } + + @Test + public void testOnBiometricError() { + String errorMessage = "test_error_message"; + mCommandQueue.onBiometricError(errorMessage); + waitForIdleSync(); + verify(mCallbacks).onBiometricError(eq(errorMessage)); + } + + @Test + public void testHideBiometricDialog() { + mCommandQueue.hideBiometricDialog(); + waitForIdleSync(); + verify(mCallbacks).hideBiometricDialog(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 9ae9ceb2629f..e691b7dec0d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -228,6 +228,18 @@ public class NetworkControllerBaseTest extends SysuiTestCase { NetworkControllerImpl.Config.add5GIconMapping("connected:5g", mConfig); } + public void setupNr5GIconConfigurationForNotRestrictedRrcCon() { + NetworkControllerImpl.Config.add5GIconMapping("connected_mmwave:5g_plus", mConfig); + NetworkControllerImpl.Config.add5GIconMapping("connected:5g_plus", mConfig); + NetworkControllerImpl.Config.add5GIconMapping("not_restricted_rrc_con:5g", mConfig); + } + + public void setupNr5GIconConfigurationForNotRestrictedRrcIdle() { + NetworkControllerImpl.Config.add5GIconMapping("connected_mmwave:5g_plus", mConfig); + NetworkControllerImpl.Config.add5GIconMapping("connected:5g_plus", mConfig); + NetworkControllerImpl.Config.add5GIconMapping("not_restricted_rrc_idle:5g", mConfig); + } + public void setConnectivityViaBroadcast( int networkType, boolean validated, boolean isConnected) { setConnectivityCommon(networkType, validated, isConnected); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index 5128675e2723..f0394da6f479 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -175,6 +175,35 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { } @Test + public void testNr5GIcon_NrNotRestrictedRrcCon_show5GIcon() { + setupNr5GIconConfigurationForNotRestrictedRrcCon(); + setupDefaultSignal(); + updateDataConnectionState(TelephonyManager.DATA_CONNECTED, + TelephonyManager.NETWORK_TYPE_LTE); + updateDataActivity(TelephonyManager.DATA_ACTIVITY_INOUT); + ServiceState ss = Mockito.mock(ServiceState.class); + doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(ss).getNrState(); + mPhoneStateListener.onServiceStateChanged(ss); + + verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, TelephonyIcons.ICON_5G, + true, DEFAULT_QS_SIGNAL_STRENGTH, TelephonyIcons.ICON_5G, true, true); + } + + @Test + public void testNr5GIcon_NrNotRestrictedRrcIdle_show5GIcon() { + setupNr5GIconConfigurationForNotRestrictedRrcIdle(); + setupDefaultSignal(); + updateDataConnectionState(TelephonyManager.DATA_CONNECTED, + TelephonyManager.NETWORK_TYPE_LTE); + updateDataActivity(TelephonyManager.DATA_ACTIVITY_DORMANT); + ServiceState ss = Mockito.mock(ServiceState.class); + doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(ss).getNrState(); + mPhoneStateListener.onServiceStateChanged(ss); + + verifyDataIndicators(TelephonyIcons.ICON_5G); + } + + @Test public void testNr5GIcon_NrConnectedWithoutMMWave_show5GIcon() { setupDefaultNr5GIconConfiguration(); setupDefaultSignal(); diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 135615708037..5e9c08b4d480 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -348,13 +348,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - protected abstract boolean isCalledForCurrentUserLocked(); + protected abstract boolean hasRightsToCurrentUserLocked(); @Override public List<AccessibilityWindowInfo> getWindows() { ensureWindowsAvailableTimed(); synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } final boolean permissionGranted = @@ -387,7 +387,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public AccessibilityWindowInfo getWindow(int windowId) { ensureWindowsAvailableTimed(); synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } final boolean permissionGranted = @@ -420,7 +420,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -481,7 +481,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -542,7 +542,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationSpec spec; synchronized (mLock) { mUsesAccessibilityCache = true; - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -602,7 +602,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked( @@ -663,7 +663,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ Region partialInteractiveRegion = Region.obtain(); MagnificationSpec spec; synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return null; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -728,7 +728,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ throws RemoteException { final int resolvedWindowId; synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); @@ -748,7 +748,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean performGlobalAction(int action) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } } @@ -771,7 +771,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationScale(int displayId) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return 1.0f; } } @@ -787,7 +787,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public Region getMagnificationRegion(int displayId) { synchronized (mLock) { final Region region = Region.obtain(); - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return region; } MagnificationController magnificationController = @@ -810,7 +810,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterX(int displayId) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return 0.0f; } MagnificationController magnificationController = @@ -832,7 +832,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public float getMagnificationCenterY(int displayId) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return 0.0f; } MagnificationController magnificationController = @@ -864,7 +864,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public boolean resetMagnification(int displayId, boolean animate) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } if (!mSecurityPolicy.canControlMagnification(this)) { @@ -886,7 +886,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } if (!mSecurityPolicy.canControlMagnification(this)) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 315d6fa287f2..b88b24e21c2a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -468,7 +468,7 @@ public class AccessibilitySecurityPolicy { } } - private boolean hasPermission(String permission) { + boolean hasPermission(String permission) { return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 02f7821bd0d5..d7f61e5371d5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -18,6 +18,7 @@ package com.android.server.accessibility; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.content.ComponentName; @@ -27,6 +28,7 @@ import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; @@ -211,19 +213,31 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } @Override - protected boolean isCalledForCurrentUserLocked() { + protected boolean hasRightsToCurrentUserLocked() { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below // performs the current profile parent resolution. - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT); - return resolvedUserId == mSystemSupport.getCurrentUserIdLocked(); + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.ROOT_UID + || callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return true; + } + if (mSecurityPolicy.resolveProfileParentLocked(UserHandle.getUserId(callingUid)) + == mSystemSupport.getCurrentUserIdLocked()) { + return true; + } + if (mSecurityPolicy.hasPermission(Manifest.permission.INTERACT_ACROSS_USERS) + || mSecurityPolicy.hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) { + return true; + } + return false; } @Override public boolean setSoftKeyboardShowMode(int showMode) { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } final UserState userState = mUserStateWeakReference.get(); @@ -241,7 +255,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean isAccessibilityButtonAvailable() { synchronized (mLock) { - if (!isCalledForCurrentUserLocked()) { + if (!hasRightsToCurrentUserLocked()) { return false; } UserState userState = mUserStateWeakReference.get(); diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 2698b72fdb36..79d975dac2b2 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -66,6 +66,7 @@ class UiAutomationManager { mUiAutomationServiceOwner.unlinkToDeath(this, 0); mUiAutomationServiceOwner = null; destroyUiAutomationService(); + Slog.v(LOG_TAG, "UiAutomation service owner died"); } }; @@ -263,7 +264,7 @@ class UiAutomationManager { } @Override - protected boolean isCalledForCurrentUserLocked() { + protected boolean hasRightsToCurrentUserLocked() { // Allow UiAutomation to work for any user return true; } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 23afce6c94e1..222a6f210448 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2014 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. @@ -18,12 +18,56 @@ package com.android.server.backup; import static com.android.internal.util.Preconditions.checkNotNull; +import static java.util.Collections.emptySet; + +import android.Manifest; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.admin.DevicePolicyManager; +import android.app.backup.BackupManager; +import android.app.backup.IBackupManager; +import android.app.backup.IBackupManagerMonitor; +import android.app.backup.IBackupObserver; +import android.app.backup.IFullBackupRestoreObserver; +import android.app.backup.IRestoreSession; +import android.app.backup.ISelectBackupTransportCallback; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.FileUtils; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.Trace; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.DumpUtils; +import com.android.server.SystemConfig; import com.android.server.SystemService; +import com.android.server.backup.utils.RandomAccessFileUtils; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Set; /** * Definition of the system service that performs backup/restore operations. @@ -31,53 +75,1502 @@ import com.android.server.SystemService; * <p>This class is responsible for handling user-aware operations and acts as a delegator, routing * incoming calls to the appropriate per-user {@link UserBackupManagerService} to handle the * corresponding backup/restore operation. + * + * <p>It also determines whether the backup service is available. It can be disabled in the + * following two ways: + * + * <ul> + * <li>Temporary - call {@link #setBackupServiceActive(int, boolean)}, or + * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true. + * </ul> + * + * Temporary disabling is controlled by {@link #setBackupServiceActive(int, boolean)} through + * privileged callers (currently {@link DevicePolicyManager}). If called on {@link + * UserHandle#USER_SYSTEM}, backup is disabled for all users. */ -public class BackupManagerService { +public class BackupManagerService extends IBackupManager.Stub { public static final String TAG = "BackupManagerService"; public static final boolean DEBUG = true; public static final boolean MORE_DEBUG = false; public static final boolean DEBUG_SCHEDULING = true; + @VisibleForTesting + static final String DUMP_RUNNING_USERS_MESSAGE = "Backup Manager is running for users:"; + + /** + * Name of file that disables the backup service. If this file exists, then backup is disabled + * for all users. + */ + private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; + + /** + * Name of file for non-system users that enables the backup service for the user. Backup is + * disabled by default in non-system users. + */ + private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated"; + + /** + * Name of file for non-system users that remembers whether backup was explicitly activated or + * deactivated with a call to setBackupServiceActive. + */ + private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated"; + + // Product-level suppression of backup/restore. + private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; + + private static final String BACKUP_THREAD = "backup"; + + static BackupManagerService sInstance; + + static BackupManagerService getInstance() { + return checkNotNull(sInstance); + } + private final Context mContext; - private final Trampoline mTrampoline; - private final SparseArray<UserBackupManagerService> mServiceUsers; + private final UserManager mUserManager; + + private final boolean mGlobalDisable; + // Lock to write backup suppress files. + // TODD(b/121198006): remove this object and synchronized all methods on "this". + private final Object mStateLock = new Object(); + + private final Handler mHandler; + private final Set<ComponentName> mTransportWhitelist; + + /** Keeps track of all unlocked users registered with this service. Indexed by user id. */ + private final SparseArray<UserBackupManagerService> mUserServices; + + private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId > 0) { // for only non system users + mHandler.post(() -> onRemovedNonSystemUser(userId)); + } + } + } + }; + + public BackupManagerService(Context context) { + this(context, new SparseArray<>()); + } + + @VisibleForTesting + BackupManagerService(Context context, SparseArray<UserBackupManagerService> userServices) { + mContext = context; + mGlobalDisable = isBackupDisabled(); + HandlerThread handlerThread = + new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + mHandler = new Handler(handlerThread.getLooper()); + mUserManager = UserManager.get(context); + mUserServices = userServices; + Set<ComponentName> transportWhitelist = + SystemConfig.getInstance().getBackupTransportWhitelist(); + mTransportWhitelist = (transportWhitelist == null) ? emptySet() : transportWhitelist; + mContext.registerReceiver( + mUserRemovedReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED)); + } + + // TODO: Remove this when we implement DI by injecting in the construtor. + @VisibleForTesting + Handler getBackupHandler() { + return mHandler; + } + + protected boolean isBackupDisabled() { + return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); + } + + protected int binderGetCallingUserId() { + return Binder.getCallingUserHandle().getIdentifier(); + } + + protected int binderGetCallingUid() { + return Binder.getCallingUid(); + } + + /** Stored in the system user's directory. */ + protected File getSuppressFileForSystemUser() { + return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), + BACKUP_SUPPRESS_FILENAME); + } + + /** Stored in the system user's directory and the file is indexed by the user it refers to. */ + protected File getRememberActivatedFileForNonSystemUser(int userId) { + return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId); + } + + /** Stored in the system user's directory and the file is indexed by the user it refers to. */ + protected File getActivatedFileForNonSystemUser(int userId) { + return UserBackupManagerFiles.getStateFileInSystemDir(BACKUP_ACTIVATED_FILENAME, userId); + } + + /** + * Remove backup state for non system {@code userId} when the user is removed from the device. + * For non system users, backup state is stored in both the user's own dir and the system dir. + * When the user is removed, the user's own dir gets removed by the OS. This method ensures that + * the part of the user backup state which is in the system dir also gets removed. + */ + private void onRemovedNonSystemUser(int userId) { + Slog.i(TAG, "Removing state for non system user " + userId); + File dir = UserBackupManagerFiles.getStateDirInSystemDir(userId); + if (!FileUtils.deleteContentsAndDir(dir)) { + Slog.w(TAG, "Failed to delete state dir for removed user: " + userId); + } + } + + // TODO (b/124359804) move to util method in FileUtils + private void createFile(File file) throws IOException { + if (file.exists()) { + return; + } + + file.getParentFile().mkdirs(); + if (!file.createNewFile()) { + Slog.w(TAG, "Failed to create file " + file.getPath()); + } + } + + // TODO (b/124359804) move to util method in FileUtils + private void deleteFile(File file) { + if (!file.exists()) { + return; + } + + if (!file.delete()) { + Slog.w(TAG, "Failed to delete file " + file.getPath()); + } + } + + /** + * Deactivates the backup service for user {@code userId}. If this is the system user, it + * creates a suppress file which disables backup for all users. If this is a non-system user, it + * only deactivates backup for that user by deleting its activate file. + */ + @GuardedBy("mStateLock") + private void deactivateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + createFile(getSuppressFileForSystemUser()); + } else { + deleteFile(getActivatedFileForNonSystemUser(userId)); + } + } + + /** + * Enables the backup service for user {@code userId}. If this is the system user, it deletes + * the suppress file. If this is a non-system user, it creates the user's activate file. Note, + * deleting the suppress file does not automatically enable backup for non-system users, they + * need their own activate file in order to participate in the service. + */ + @GuardedBy("mStateLock") + private void activateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + deleteFile(getSuppressFileForSystemUser()); + } else { + createFile(getActivatedFileForNonSystemUser(userId)); + } + } + + // This method should not perform any I/O (e.g. do not call isBackupActivatedForUser), + // it's used in multiple places where I/O waits would cause system lock-ups. + private boolean isUserReadyForBackup(int userId) { + return mUserServices.get(UserHandle.USER_SYSTEM) != null + && mUserServices.get(userId) != null; + } + + /** + * Backup is activated for the system user if the suppress file does not exist. Backup is + * activated for non-system users if the suppress file does not exist AND the user's activated + * file exists. + */ + private boolean isBackupActivatedForUser(int userId) { + if (getSuppressFileForSystemUser().exists()) { + return false; + } + + return userId == UserHandle.USER_SYSTEM + || getActivatedFileForNonSystemUser(userId).exists(); + } + + protected Context getContext() { + return mContext; + } + + protected UserManager getUserManager() { + return mUserManager; + } + + protected void postToHandler(Runnable runnable) { + mHandler.post(runnable); + } + + /** + * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked. + * Starts the backup service for this user if backup is active for this user. Offloads work onto + * the handler thread {@link #mHandlerThread} to keep unlock time low since backup is not + * essential for device functioning. + */ + void onUnlockUser(int userId) { + postToHandler(() -> startServiceForUser(userId)); + } + + /** + * Starts the backup service for user {@code userId} by creating a new instance of {@link + * UserBackupManagerService} and registering it with this service. + */ + @VisibleForTesting + void startServiceForUser(int userId) { + // We know that the user is unlocked here because it is called from setBackupServiceActive + // and unlockUser which have these guarantees. So we can check if the file exists. + if (mGlobalDisable) { + Slog.i(TAG, "Backup service not supported"); + return; + } + if (!isBackupActivatedForUser(userId)) { + Slog.i(TAG, "Backup not activated for user " + userId); + return; + } + if (mUserServices.get(userId) != null) { + Slog.i(TAG, "userId " + userId + " already started, so not starting again"); + return; + } + Slog.i(TAG, "Starting service for user: " + userId); + UserBackupManagerService userBackupManagerService = + UserBackupManagerService.createAndInitializeService( + userId, mContext, this, mTransportWhitelist); + startServiceForUser(userId, userBackupManagerService); + } + + /** + * Starts the backup service for user {@code userId} by registering its instance of {@link + * UserBackupManagerService} with this service and setting enabled state. + */ + void startServiceForUser(int userId, UserBackupManagerService userBackupManagerService) { + mUserServices.put(userId, userBackupManagerService); + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable"); + userBackupManagerService.initializeBackupEnableState(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + /** Stops the backup service for user {@code userId} when the user is stopped. */ + @VisibleForTesting + protected void stopServiceForUser(int userId) { + UserBackupManagerService userBackupManagerService = mUserServices.removeReturnOld(userId); + + if (userBackupManagerService != null) { + userBackupManagerService.tearDownService(); + + KeyValueBackupJob.cancel(userId, mContext); + FullBackupJob.cancel(userId, mContext); + } + } + + /** + * Returns a list of users currently unlocked that have a {@link UserBackupManagerService} + * registered. + * + * Warning: Do NOT modify returned object as it's used inside. + * + * TODO: Return a copy or only expose read-only information through other means. + */ + @VisibleForTesting + SparseArray<UserBackupManagerService> getUserServices() { + return mUserServices; + } + + /** + * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is stopped. + * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low. + */ + void onStopUser(int userId) { + postToHandler( + () -> { + if (!mGlobalDisable) { + Slog.i(TAG, "Stopping service for user: " + userId); + stopServiceForUser(userId); + } + }); + } + + /** Returns {@link UserBackupManagerService} for user {@code userId}. */ + @Nullable + public UserBackupManagerService getUserService(int userId) { + return mUserServices.get(userId); + } + + /** + * The system user and managed profiles can only be acted on by callers in the system or root + * processes. Other users can be acted on by callers who have both android.permission.BACKUP and + * android.permission.INTERACT_ACROSS_USERS_FULL permissions. + */ + private void enforcePermissionsOnUser(int userId) throws SecurityException { + boolean isRestrictedUser = + userId == UserHandle.USER_SYSTEM + || getUserManager().getUserInfo(userId).isManagedProfile(); + + if (isRestrictedUser) { + int caller = binderGetCallingUid(); + if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { + throw new SecurityException("No permission to configure backup activity"); + } + } else { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.BACKUP, "No permission to configure backup activity"); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "No permission to configure backup activity"); + } + } + + /** + * Only privileged callers should be changing the backup state. Deactivating backup in the + * system user also deactivates backup in all users. We are not guaranteed that {@code userId} + * is unlocked at this point yet, so handle both cases. + */ + public void setBackupServiceActive(int userId, boolean makeActive) { + enforcePermissionsOnUser(userId); + + // In Q, backup is OFF by default for non-system users. In the future, we will change that + // to ON unless backup was explicitly deactivated with a (permissioned) call to + // setBackupServiceActive. + // Therefore, remember this for use in the future. Basically the default in the future will + // be: rememberFile.exists() ? rememberFile.value() : ON + // Note that this has to be done right after the permission checks and before any other + // action since we need to remember that a permissioned call was made irrespective of + // whether the call changes the state or not. + if (userId != UserHandle.USER_SYSTEM) { + try { + File rememberFile = getRememberActivatedFileForNonSystemUser(userId); + createFile(rememberFile); + RandomAccessFileUtils.writeBoolean(rememberFile, makeActive); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service activity", e); + } + } + + if (mGlobalDisable) { + Slog.i(TAG, "Backup service not supported"); + return; + } + + synchronized (mStateLock) { + Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active"); + if (makeActive) { + try { + activateBackupForUserLocked(userId); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service activity"); + } + + // If the user is unlocked, we can start the backup service for it. Otherwise we + // will start the service when the user is unlocked as part of its unlock callback. + if (getUserManager().isUserUnlocked(userId)) { + // Clear calling identity as initialization enforces the system identity but we + // can be coming from shell. + long oldId = Binder.clearCallingIdentity(); + try { + startServiceForUser(userId); + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + } else { + try { + //TODO(b/121198006): what if this throws an exception? + deactivateBackupForUserLocked(userId); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service inactivity"); + } + //TODO(b/121198006): loop through active users that have work profile and + // stop them as well. + onStopUser(userId); + } + } + } + + // IBackupManager binder API + + /** + * Querying activity state of backup service. + * + * @param userId The user in which the activity state of backup service is queried. + * @return true if the service is active. + */ + @Override + public boolean isBackupServiceActive(int userId) { + synchronized (mStateLock) { + return !mGlobalDisable && isBackupActivatedForUser(userId); + } + } + + @Override + public void dataChangedForUser(int userId, String packageName) throws RemoteException { + if (isUserReadyForBackup(userId)) { + dataChanged(userId, packageName); + } + } + + @Override + public void dataChanged(String packageName) throws RemoteException { + dataChangedForUser(binderGetCallingUserId(), packageName); + } + + /** + * An app's backup agent calls this method to let the service know that there's new data to + * backup for their app {@code packageName}. Only used for apps participating in key-value + * backup. + */ + public void dataChanged(@UserIdInt int userId, String packageName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "dataChanged()"); + + if (userBackupManagerService != null) { + userBackupManagerService.dataChanged(packageName); + } + } + + // --------------------------------------------- + // TRANSPORT OPERATIONS + // --------------------------------------------- + + @Override + public void initializeTransportsForUser( + int userId, String[] transportNames, IBackupObserver observer) throws RemoteException { + if (isUserReadyForBackup(userId)) { + initializeTransports(userId, transportNames, observer); + } + } + + /** Run an initialize operation for the given transports {@code transportNames}. */ + public void initializeTransports( + @UserIdInt int userId, String[] transportNames, IBackupObserver observer) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "initializeTransports()"); + + if (userBackupManagerService != null) { + userBackupManagerService.initializeTransports(transportNames, observer); + } + } + + @Override + public void clearBackupDataForUser(int userId, String transportName, String packageName) + throws RemoteException { + if (isUserReadyForBackup(userId)) { + clearBackupData(userId, transportName, packageName); + } + } + + /** + * Clear the given package {@code packageName}'s backup data from the transport {@code + * transportName}. + */ + public void clearBackupData(@UserIdInt int userId, String transportName, String packageName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "clearBackupData()"); + + if (userBackupManagerService != null) { + userBackupManagerService.clearBackupData(transportName, packageName); + } + } + + @Override + public void clearBackupData(String transportName, String packageName) + throws RemoteException { + clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName); + } + + @Override + public void agentConnectedForUser(int userId, String packageName, IBinder agent) + throws RemoteException { + if (isUserReadyForBackup(userId)) { + agentConnected(userId, packageName, agent); + } + } + + @Override + public void agentConnected(String packageName, IBinder agent) throws RemoteException { + agentConnectedForUser(binderGetCallingUserId(), packageName, agent); + } + + /** + * Callback: a requested backup agent has been instantiated. This should only be called from the + * {@link ActivityManager}. + */ + public void agentConnected(@UserIdInt int userId, String packageName, IBinder agentBinder) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "agentConnected()"); + + if (userBackupManagerService != null) { + userBackupManagerService.agentConnected(packageName, agentBinder); + } + } + + @Override + public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException { + if (isUserReadyForBackup(userId)) { + agentDisconnected(userId, packageName); + } + } + + @Override + public void agentDisconnected(String packageName) throws RemoteException { + agentDisconnectedForUser(binderGetCallingUserId(), packageName); + } + + /** + * Callback: a backup agent has failed to come up, or has unexpectedly quit. This should only be + * called from the {@link ActivityManager}. + */ + public void agentDisconnected(@UserIdInt int userId, String packageName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()"); + + if (userBackupManagerService != null) { + userBackupManagerService.agentDisconnected(packageName); + } + } + + @Override + public void restoreAtInstallForUser(int userId, String packageName, int token) + throws RemoteException { + if (isUserReadyForBackup(userId)) { + restoreAtInstall(userId, packageName, token); + } + } + + @Override + public void restoreAtInstall(String packageName, int token) throws RemoteException { + restoreAtInstallForUser(binderGetCallingUserId(), packageName, token); + } + + /** + * Used to run a restore pass for an application that is being installed. This should only be + * called from the {@link PackageManager}. + */ + public void restoreAtInstall(@UserIdInt int userId, String packageName, int token) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "restoreAtInstall()"); + + if (userBackupManagerService != null) { + userBackupManagerService.restoreAtInstall(packageName, token); + } + } + + @Override + public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled) + throws RemoteException { + if (isUserReadyForBackup(userId)) { + setBackupEnabled(userId, isEnabled); + } + } + + @Override + public void setBackupEnabled(boolean isEnabled) throws RemoteException { + setBackupEnabledForUser(binderGetCallingUserId(), isEnabled); + } + + /** Enable/disable the backup service. This is user-configurable via backup settings. */ + public void setBackupEnabled(@UserIdInt int userId, boolean enable) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "setBackupEnabled()"); + + if (userBackupManagerService != null) { + userBackupManagerService.setBackupEnabled(enable); + } + } + + @Override + public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException { + if (isUserReadyForBackup(userId)) { + setAutoRestore(userId, doAutoRestore); + } + } + + @Override + public void setAutoRestore(boolean doAutoRestore) throws RemoteException { + setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore); + } + + /** Enable/disable automatic restore of app data at install time. */ + public void setAutoRestore(@UserIdInt int userId, boolean autoRestore) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "setAutoRestore()"); + + if (userBackupManagerService != null) { + userBackupManagerService.setAutoRestore(autoRestore); + } + } + + @Override + public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException { + return isUserReadyForBackup(userId) && isBackupEnabled(userId); + } + + @Override + public boolean isBackupEnabled() throws RemoteException { + return isBackupEnabledForUser(binderGetCallingUserId()); + } + + /** + * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}. + */ + public boolean isBackupEnabled(@UserIdInt int userId) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "isBackupEnabled()"); + + return userBackupManagerService != null && userBackupManagerService.isBackupEnabled(); + } + + /** Sets the backup password used when running adb backup. */ + @Override + public boolean setBackupPassword(String currentPassword, String newPassword) { + int userId = binderGetCallingUserId(); + if (!isUserReadyForBackup(userId)) { + return false; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission( + UserHandle.USER_SYSTEM, "setBackupPassword()"); + + return userBackupManagerService != null + && userBackupManagerService.setBackupPassword(currentPassword, newPassword); + } + + /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */ + @Override + public boolean hasBackupPassword() throws RemoteException { + int userId = binderGetCallingUserId(); + if (!isUserReadyForBackup(userId)) { + return false; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission( + UserHandle.USER_SYSTEM, "hasBackupPassword()"); + + return userBackupManagerService != null && userBackupManagerService.hasBackupPassword(); + } + + @Override + public void backupNowForUser(@UserIdInt int userId) throws RemoteException { + if (isUserReadyForBackup(userId)) { + backupNow(userId); + } + } + + @Override + public void backupNow() throws RemoteException { + backupNowForUser(binderGetCallingUserId()); + } + + /** + * Run a backup pass immediately for any key-value backup applications that have declared that + * they have pending updates. + */ + public void backupNow(@UserIdInt int userId) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "backupNow()"); + + if (userBackupManagerService != null) { + userBackupManagerService.backupNow(); + } + } + + /** + * Used by 'adb backup' to run a backup pass for packages {@code packageNames} supplied via the + * command line, writing the resulting data stream to the supplied {@code fd}. This method is + * synchronous and does not return to the caller until the backup has been completed. It + * requires on-screen confirmation by the user. + */ + @Override + public void adbBackup( + @UserIdInt int userId, + ParcelFileDescriptor fd, + boolean includeApks, + boolean includeObbs, + boolean includeShared, + boolean doWidgets, + boolean doAllApps, + boolean includeSystem, + boolean doCompress, + boolean doKeyValue, + String[] packageNames) { + if (!isUserReadyForBackup(userId)) { + return; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "adbBackup()"); + + if (userBackupManagerService != null) { + userBackupManagerService.adbBackup( + fd, + includeApks, + includeObbs, + includeShared, + doWidgets, + doAllApps, + includeSystem, + doCompress, + doKeyValue, + packageNames); + } + } + + @Override + public void fullTransportBackupForUser(int userId, String[] packageNames) + throws RemoteException { + if (isUserReadyForBackup(userId)) { + fullTransportBackup(userId, packageNames); + } + } + + /** + * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'. + */ + public void fullTransportBackup(@UserIdInt int userId, String[] packageNames) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "fullTransportBackup()"); + + if (userBackupManagerService != null) { + userBackupManagerService.fullTransportBackup(packageNames); + } + } + + /** + * Used by 'adb restore' to run a restore pass reading from the supplied {@code fd}. This method + * is synchronous and does not return to the caller until the restore has been completed. It + * requires on-screen confirmation by the user. + */ + @Override + public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) { + if (!isUserReadyForBackup(userId)) { + return; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "adbRestore()"); + + if (userBackupManagerService != null) { + userBackupManagerService.adbRestore(fd); + } + } + + @Override + public void acknowledgeFullBackupOrRestoreForUser( + int userId, + int token, + boolean allow, + String curPassword, + String encryptionPassword, + IFullBackupRestoreObserver observer) + throws RemoteException { + if (isUserReadyForBackup(userId)) { + acknowledgeAdbBackupOrRestore(userId, token, allow, + curPassword, encryptionPassword, observer); + } + } + + /** + * Confirm that the previously requested adb backup/restore operation can proceed. This is used + * to require a user-facing disclosure about the operation. + */ + public void acknowledgeAdbBackupOrRestore( + @UserIdInt int userId, + int token, + boolean allow, + String currentPassword, + String encryptionPassword, + IFullBackupRestoreObserver observer) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "acknowledgeAdbBackupOrRestore()"); + + if (userBackupManagerService != null) { + userBackupManagerService.acknowledgeAdbBackupOrRestore( + token, allow, currentPassword, encryptionPassword, observer); + } + } + + @Override + public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, + String encryptionPassword, IFullBackupRestoreObserver observer) + throws RemoteException { + acknowledgeFullBackupOrRestoreForUser( + binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer); + } + + + @Override + public String getCurrentTransportForUser(int userId) throws RemoteException { + return (isUserReadyForBackup(userId)) ? getCurrentTransport(userId) : null; + } + + @Override + public String getCurrentTransport() throws RemoteException { + return getCurrentTransportForUser(binderGetCallingUserId()); + } + + /** Return the name of the currently active transport. */ + @Nullable + public String getCurrentTransport(@UserIdInt int userId) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "getCurrentTransport()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getCurrentTransport(); + } + + /** + * Returns the {@link ComponentName} of the host service of the selected transport or + * {@code null} if no transport selected or if the transport selected is not registered. + */ + @Override + @Nullable + public ComponentName getCurrentTransportComponentForUser(int userId) { + return (isUserReadyForBackup(userId)) ? getCurrentTransportComponent(userId) : null; + } + + /** + * Returns the {@link ComponentName} of the host service of the selected transport or {@code + * null} if no transport selected or if the transport selected is not registered. + */ + @Nullable + public ComponentName getCurrentTransportComponent(@UserIdInt int userId) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "getCurrentTransportComponent()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getCurrentTransportComponent(); + } + + @Override + public String[] listAllTransportsForUser(int userId) throws RemoteException { + return (isUserReadyForBackup(userId)) ? listAllTransports(userId) : null; + } + + /** Report all known, available backup transports by name. */ + @Nullable + public String[] listAllTransports(@UserIdInt int userId) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "listAllTransports()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.listAllTransports(); + } + + @Override + public String[] listAllTransports() throws RemoteException { + return listAllTransportsForUser(binderGetCallingUserId()); + } + + @Override + public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException { + return (isUserReadyForBackup(userId)) + ? listAllTransportComponents(userId) : null; + } + + /** Report all known, available backup transports by {@link ComponentName}. */ + @Nullable + public ComponentName[] listAllTransportComponents(@UserIdInt int userId) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "listAllTransportComponents()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.listAllTransportComponents(); + } + + @Override + public String[] getTransportWhitelist() { + int userId = binderGetCallingUserId(); + if (!isUserReadyForBackup(userId)) { + return null; + } + // No permission check, intentionally. + String[] whitelistedTransports = new String[mTransportWhitelist.size()]; + int i = 0; + for (ComponentName component : mTransportWhitelist) { + whitelistedTransports[i] = component.flattenToShortString(); + i++; + } + return whitelistedTransports; + } + + @Override + public void updateTransportAttributesForUser( + int userId, + ComponentName transportComponent, + String name, + @Nullable Intent configurationIntent, + String currentDestinationString, + @Nullable Intent dataManagementIntent, + CharSequence dataManagementLabel) { + if (isUserReadyForBackup(userId)) { + updateTransportAttributes( + userId, + transportComponent, + name, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + } + + /** + * Update the attributes of the transport identified by {@code transportComponent}. If the + * specified transport has not been bound at least once (for registration), this call will be + * ignored. Only the host process of the transport can change its description, otherwise a + * {@link SecurityException} will be thrown. + * + * @param transportComponent The identity of the transport being described. + * @param name A {@link String} with the new name for the transport. This is NOT for + * identification. MUST NOT be {@code null}. + * @param configurationIntent An {@link Intent} that can be passed to {@link + * Context#startActivity} in order to launch the transport's configuration UI. It may be + * {@code null} if the transport does not offer any user-facing configuration UI. + * @param currentDestinationString A {@link String} describing the destination to which the + * transport is currently sending data. MUST NOT be {@code null}. + * @param dataManagementIntent An {@link Intent} that can be passed to {@link + * Context#startActivity} in order to launch the transport's data-management UI. It may be + * {@code null} if the transport does not offer any user-facing data management UI. + * @param dataManagementLabel A {@link CharSequence} to be used as the label for the transport's + * data management affordance. This MUST be {@code null} when dataManagementIntent is {@code + * null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}. + * @throws SecurityException If the UID of the calling process differs from the package UID of + * {@code transportComponent} or if the caller does NOT have BACKUP permission. + */ + public void updateTransportAttributes( + @UserIdInt int userId, + ComponentName transportComponent, + String name, + @Nullable Intent configurationIntent, + String currentDestinationString, + @Nullable Intent dataManagementIntent, + CharSequence dataManagementLabel) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "updateTransportAttributes()"); + + if (userBackupManagerService != null) { + userBackupManagerService.updateTransportAttributes( + transportComponent, + name, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + } + + @Override + public String selectBackupTransportForUser(int userId, String transport) + throws RemoteException { + return (isUserReadyForBackup(userId)) + ? selectBackupTransport(userId, transport) : null; + } + + @Override + public String selectBackupTransport(String transport) throws RemoteException { + return selectBackupTransportForUser(binderGetCallingUserId(), transport); + } + + /** + * Selects transport {@code transportName} and returns the previously selected transport. + * + * @deprecated Use {@link #selectBackupTransportAsync(ComponentName, + * ISelectBackupTransportCallback)} instead. + */ + @Deprecated + @Nullable + public String selectBackupTransport(@UserIdInt int userId, String transportName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "selectBackupTransport()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.selectBackupTransport(transportName); + } + + @Override + public void selectBackupTransportAsyncForUser(int userId, ComponentName transport, + ISelectBackupTransportCallback listener) throws RemoteException { + if (isUserReadyForBackup(userId)) { + selectBackupTransportAsync(userId, transport, listener); + } else { + if (listener != null) { + try { + listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED); + } catch (RemoteException ex) { + // ignore + } + } + } + } + + /** + * Selects transport {@code transportComponent} asynchronously and notifies {@code listener} + * with the result upon completion. + */ + public void selectBackupTransportAsync( + @UserIdInt int userId, + ComponentName transportComponent, + ISelectBackupTransportCallback listener) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "selectBackupTransportAsync()"); + + if (userBackupManagerService != null) { + userBackupManagerService.selectBackupTransportAsync(transportComponent, listener); + } + } + + @Override + public Intent getConfigurationIntentForUser(int userId, String transport) + throws RemoteException { + return isUserReadyForBackup(userId) ? getConfigurationIntent(userId, transport) + : null; + } + + @Override + public Intent getConfigurationIntent(String transport) + throws RemoteException { + return getConfigurationIntentForUser(binderGetCallingUserId(), transport); + } + + /** + * Supply the configuration intent for the given transport. If the name is not one of the + * available transports, or if the transport does not supply any configuration UI, the method + * returns {@code null}. + */ + @Nullable + public Intent getConfigurationIntent(@UserIdInt int userId, String transportName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "getConfigurationIntent()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getConfigurationIntent(transportName); + } + + @Override + public String getDestinationStringForUser(int userId, String transport) throws RemoteException { + return isUserReadyForBackup(userId) ? getDestinationString(userId, transport) + : null; + } + + @Override + public String getDestinationString(String transport) throws RemoteException { + return getDestinationStringForUser(binderGetCallingUserId(), transport); + } + + /** + * Supply the current destination string for the given transport. If the name is not one of the + * registered transports the method will return null. + * + * <p>This string is used VERBATIM as the summary text of the relevant Settings item. + * + * @param transportName The name of the registered transport. + * @return The current destination string or null if the transport is not registered. + */ + @Nullable + public String getDestinationString(@UserIdInt int userId, String transportName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "getDestinationString()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getDestinationString(transportName); + } + + @Override + public Intent getDataManagementIntentForUser(int userId, String transport) + throws RemoteException { + return isUserReadyForBackup(userId) + ? getDataManagementIntent(userId, transport) : null; + } + + @Override + public Intent getDataManagementIntent(String transport) + throws RemoteException { + return getDataManagementIntentForUser(binderGetCallingUserId(), transport); + } + + /** Supply the manage-data intent for the given transport. */ + @Nullable + public Intent getDataManagementIntent(@UserIdInt int userId, String transportName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "getDataManagementIntent()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getDataManagementIntent(transportName); + } + + @Override + public CharSequence getDataManagementLabelForUser(int userId, String transport) + throws RemoteException { + return isUserReadyForBackup(userId) ? getDataManagementLabel(userId, transport) + : null; + } + + /** + * Supply the menu label for affordances that fire the manage-data intent for the given + * transport. + */ + @Nullable + public CharSequence getDataManagementLabel(@UserIdInt int userId, String transportName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "getDataManagementLabel()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.getDataManagementLabel(transportName); + } + + @Override + public IRestoreSession beginRestoreSessionForUser( + int userId, String packageName, String transportID) throws RemoteException { + return isUserReadyForBackup(userId) + ? beginRestoreSession(userId, packageName, transportID) : null; + } + + /** + * Begin a restore for the specified package {@code packageName} using the specified transport + * {@code transportName}. + */ + @Nullable + public IRestoreSession beginRestoreSession( + @UserIdInt int userId, String packageName, String transportName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.beginRestoreSession(packageName, transportName); + } + + @Override + public void opCompleteForUser(int userId, int token, long result) throws RemoteException { + if (isUserReadyForBackup(userId)) { + opComplete(userId, token, result); + } + } + + @Override + public void opComplete(int token, long result) throws RemoteException { + opCompleteForUser(binderGetCallingUserId(), token, result); + } + + /** + * Used by a currently-active backup agent to notify the service that it has completed its given + * outstanding asynchronous backup/restore operation. + */ + public void opComplete(@UserIdInt int userId, int token, long result) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "opComplete()"); + + if (userBackupManagerService != null) { + userBackupManagerService.opComplete(token, result); + } + } + + @Override + public long getAvailableRestoreTokenForUser(int userId, String packageName) { + return isUserReadyForBackup(userId) ? getAvailableRestoreToken(userId, packageName) : 0; + } + + /** + * Get the restore-set token for the best-available restore set for this {@code packageName}: + * the active set if possible, else the ancestral one. Returns zero if none available. + */ + public long getAvailableRestoreToken(@UserIdInt int userId, String packageName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "getAvailableRestoreToken()"); + + return userBackupManagerService == null + ? 0 + : userBackupManagerService.getAvailableRestoreToken(packageName); + } + + @Override + public boolean isAppEligibleForBackupForUser(int userId, String packageName) { + return isUserReadyForBackup(userId) && isAppEligibleForBackup(userId, + packageName); + } + + /** Checks if the given package {@code packageName} is eligible for backup. */ + public boolean isAppEligibleForBackup(@UserIdInt int userId, String packageName) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "isAppEligibleForBackup()"); + + return userBackupManagerService != null + && userBackupManagerService.isAppEligibleForBackup(packageName); + } + + @Override + public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) { + return isUserReadyForBackup(userId) ? filterAppsEligibleForBackup(userId, packages) : null; + } + + /** + * Returns from the inputted packages {@code packages}, the ones that are eligible for backup. + */ + @Nullable + public String[] filterAppsEligibleForBackup(@UserIdInt int userId, String[] packages) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "filterAppsEligibleForBackup()"); + + return userBackupManagerService == null + ? null + : userBackupManagerService.filterAppsEligibleForBackup(packages); + } + + @Override + public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver + observer, IBackupManagerMonitor monitor, int flags) throws RemoteException { + if (!isUserReadyForBackup(userId)) { + return BackupManager.ERROR_BACKUP_NOT_ALLOWED; + } + return requestBackup(userId, packages, observer, monitor, flags); + } - /** Instantiate a new instance of {@link BackupManagerService}. */ - public BackupManagerService( - Context context, - Trampoline trampoline, - SparseArray<UserBackupManagerService> userServices) { - mContext = checkNotNull(context); - mTrampoline = checkNotNull(trampoline); - // TODO(b/135661048): Remove - mServiceUsers = userServices; + @Override + public int requestBackup(String[] packages, IBackupObserver observer, + IBackupManagerMonitor monitor, int flags) throws RemoteException { + return requestBackupForUser(binderGetCallingUserId(), packages, + observer, monitor, flags); + } + + /** + * Requests a backup for the inputted {@code packages} with a specified callback {@link + * IBackupManagerMonitor} for receiving events during the operation. + */ + public int requestBackup( + @UserIdInt int userId, + String[] packages, + IBackupObserver observer, + IBackupManagerMonitor monitor, + int flags) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "requestBackup()"); + + return userBackupManagerService == null + ? BackupManager.ERROR_BACKUP_NOT_ALLOWED + : userBackupManagerService.requestBackup(packages, observer, monitor, flags); + } + + @Override + public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException { + if (isUserReadyForBackup(userId)) { + cancelBackups(userId); + } + } + + @Override + public void cancelBackups() throws RemoteException { + cancelBackupsForUser(binderGetCallingUserId()); + } + + /** Cancel all running backup operations. */ + public void cancelBackups(@UserIdInt int userId) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "cancelBackups()"); + + if (userBackupManagerService != null) { + userBackupManagerService.cancelBackups(); + } + } + + /** + * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the + * serial number of its ancestral work profile or null if there is no {@link + * UserBackupManagerService} associated with that user. + * + * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)} + * and it corresponds to the profile that was used to restore to the callers profile. + */ + @Override + @Nullable + public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) { + if (mGlobalDisable) { + return null; + } + int callingUserId = Binder.getCallingUserHandle().getIdentifier(); + long oldId = Binder.clearCallingIdentity(); + final int[] userIds; + try { + userIds = + mContext + .getSystemService(UserManager.class) + .getProfileIds(callingUserId, false); + } finally { + Binder.restoreCallingIdentity(oldId); + } + + for (int userId : userIds) { + UserBackupManagerService userBackupManagerService = mUserServices.get(userId); + if (userBackupManagerService != null) { + if (userBackupManagerService.getAncestralSerialNumber() == ancestralSerialNumber) { + return UserHandle.of(userId); + } + } + } + + return null; + } + + /** + * Sets the ancestral work profile for the calling user. + * + * <p> The ancestral work profile corresponds to the profile that was used to restore to the + * callers profile. + */ + @Override + public void setAncestralSerialNumber(long ancestralSerialNumber) { + if (mGlobalDisable) { + return; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission( + Binder.getCallingUserHandle().getIdentifier(), + "setAncestralSerialNumber()"); + + if (userBackupManagerService != null) { + userBackupManagerService.setAncestralSerialNumber(ancestralSerialNumber); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { + return; + } + int userId = binderGetCallingUserId(); + if (!isUserReadyForBackup(userId)) { + pw.println("Inactive"); + return; + } + + if (args != null) { + for (String arg : args) { + if ("users".equals(arg.toLowerCase())) { + pw.print(DUMP_RUNNING_USERS_MESSAGE); + for (int i = 0; i < mUserServices.size(); i++) { + pw.print(" " + mUserServices.keyAt(i)); + } + pw.println(); + return; + } + } + } + + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()"); + + if (userBackupManagerService != null) { + userBackupManagerService.dump(fd, pw, args); + } + } + + /** + * Used by the {@link JobScheduler} to run a full backup when conditions are right. The model we + * use is to perform one app backup per scheduled job execution, and to reschedule the job with + * zero latency as long as conditions remain right and we still have work to do. + * + * @return Whether ongoing work will continue. The return value here will be passed along as the + * return value to the callback {@link JobService#onStartJob(JobParameters)}. + */ + public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) { + if (!isUserReadyForBackup(userId)) { + return false; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()"); + + return userBackupManagerService != null + && userBackupManagerService.beginFullBackup(scheduledJob); + } + + /** + * Used by the {@link JobScheduler} to end the current full backup task when conditions are no + * longer met for running the full backup job. + */ + public void endFullBackup(@UserIdInt int userId) { + if (!isUserReadyForBackup(userId)) { + return; + } + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(userId, "endFullBackup()"); + + if (userBackupManagerService != null) { + userBackupManagerService.endFullBackup(); + } + } + + /** + * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}. + * If the user is not registered with the service (either the user is locked or not eligible for + * the backup service) then return {@code null}. + * + * @param userId The id of the user to retrieve its instance of {@link + * UserBackupManagerService}. + * @param caller A {@link String} identifying the caller for logging purposes. + * @throws SecurityException if {@code userId} is different from the calling user id and the + * caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission. + */ + @Nullable + @VisibleForTesting + UserBackupManagerService getServiceForUserIfCallerHasPermission( + @UserIdInt int userId, String caller) { + enforceCallingPermissionOnUserId(userId, caller); + UserBackupManagerService userBackupManagerService = mUserServices.get(userId); + if (userBackupManagerService == null) { + Slog.w(TAG, "Called " + caller + " for unknown user: " + userId); + } + return userBackupManagerService; + } + + /** + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id on which the backup operation is being requested. + * @param message A message to include in the exception if it is thrown. + */ + void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) { + if (Binder.getCallingUserHandle().getIdentifier() != userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); + } } /** Implementation to receive lifecycle event callbacks for system services. */ public static class Lifecycle extends SystemService { public Lifecycle(Context context) { - this(context, new Trampoline(context)); + this(context, new BackupManagerService(context)); } @VisibleForTesting - Lifecycle(Context context, Trampoline trampoline) { + Lifecycle(Context context, BackupManagerService backupManagerService) { super(context); - Trampoline.sInstance = trampoline; + sInstance = backupManagerService; } @Override public void onStart() { - publishService(Context.BACKUP_SERVICE, Trampoline.sInstance); + publishService(Context.BACKUP_SERVICE, BackupManagerService.sInstance); } @Override public void onUnlockUser(int userId) { - Trampoline.sInstance.onUnlockUser(userId); + sInstance.onUnlockUser(userId); } @Override public void onStopUser(int userId) { - Trampoline.sInstance.onStopUser(userId); + sInstance.onStopUser(userId); } @VisibleForTesting diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java index 19a85432875c..0bb25e360f15 100644 --- a/services/backup/java/com/android/server/backup/FullBackupJob.java +++ b/services/backup/java/com/android/server/backup/FullBackupJob.java @@ -91,7 +91,7 @@ public class FullBackupJob extends JobService { mParamsForUser.put(userId, params); } - Trampoline service = Trampoline.getInstance(); + BackupManagerService service = BackupManagerService.getInstance(); return service.beginFullBackup(userId, this); } @@ -105,7 +105,7 @@ public class FullBackupJob extends JobService { } } - Trampoline service = Trampoline.getInstance(); + BackupManagerService service = BackupManagerService.getInstance(); service.endFullBackup(userId); return false; diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java index 7b5dbd7ce777..058dcae3102f 100644 --- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java +++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java @@ -144,7 +144,7 @@ public class KeyValueBackupJob extends JobService { } // Time to run a key/value backup! - Trampoline service = Trampoline.getInstance(); + BackupManagerService service = BackupManagerService.getInstance(); try { service.backupNowForUser(userId); } catch (RemoteException e) {} diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java deleted file mode 100644 index 4cd1e17ca578..000000000000 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ /dev/null @@ -1,1549 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.backup; - -import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.server.backup.BackupManagerService.TAG; - -import static java.util.Collections.emptySet; - -import android.Manifest; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.ActivityManager; -import android.app.admin.DevicePolicyManager; -import android.app.backup.BackupManager; -import android.app.backup.IBackupManager; -import android.app.backup.IBackupManagerMonitor; -import android.app.backup.IBackupObserver; -import android.app.backup.IFullBackupRestoreObserver; -import android.app.backup.IRestoreSession; -import android.app.backup.ISelectBackupTransportCallback; -import android.app.job.JobParameters; -import android.app.job.JobScheduler; -import android.app.job.JobService; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.os.Binder; -import android.os.FileUtils; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.os.RemoteException; -import android.os.SystemProperties; -import android.os.Trace; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DumpUtils; -import com.android.server.SystemConfig; -import com.android.server.backup.utils.RandomAccessFileUtils; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Set; - -/** - * A proxy to the {@link BackupManagerService} implementation. - * - * <p>This is an external interface to the {@link BackupManagerService} which is being accessed via - * published binder {@link BackupManagerService.Lifecycle}. This lets us turn down the heavy - * implementation object on the fly without disturbing binders that have been cached somewhere in - * the system. - * - * <p>Trampoline determines whether the backup service is available. It can be disabled in the - * following two ways: - * - * <ul> - * <li>Temporary - create the file {@link #BACKUP_SUPPRESS_FILENAME}, or - * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true. - * </ul> - * - * Temporary disabling is controlled by {@link #setBackupServiceActive(int, boolean)} through - * privileged callers (currently {@link DevicePolicyManager}). This is called on {@link - * UserHandle#USER_SYSTEM} and disables backup for all users. - */ -public class Trampoline extends IBackupManager.Stub { - @VisibleForTesting - static final String DUMP_RUNNING_USERS_MESSAGE = "Backup Manager is running for users:"; - - /** - * Name of file that disables the backup service. If this file exists, then backup is disabled - * for all users. - */ - private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; - - /** - * Name of file for non-system users that enables the backup service for the user. Backup is - * disabled by default in non-system users. - */ - private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated"; - - /** - * Name of file for non-system users that remembers whether backup was explicitly activated or - * deactivated with a call to setBackupServiceActive. - */ - private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated"; - - // Product-level suppression of backup/restore. - private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; - - private static final String BACKUP_THREAD = "backup"; - - static Trampoline sInstance; - - static Trampoline getInstance() { - return checkNotNull(sInstance); - } - - private final Context mContext; - private final UserManager mUserManager; - - private final boolean mGlobalDisable; - // Lock to write backup suppress files. - // TODD(b/121198006): remove this object and synchronized all methods on "this". - private final Object mStateLock = new Object(); - - // TODO: This is not marked as final because of test code. Since we'll merge BMS and Trampoline, - // it doesn't make sense to refactor for final. It's never null. - @VisibleForTesting - protected volatile BackupManagerService mService; - private final Handler mHandler; - private final Set<ComponentName> mTransportWhitelist; - - /** Keeps track of all unlocked users registered with this service. Indexed by user id. */ - private final SparseArray<UserBackupManagerService> mUserServices; - - private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId > 0) { // for only non system users - mHandler.post(() -> onRemovedNonSystemUser(userId)); - } - } - } - }; - - public Trampoline(Context context) { - this(context, new SparseArray<>()); - } - - @VisibleForTesting - Trampoline(Context context, SparseArray<UserBackupManagerService> userServices) { - mContext = context; - mGlobalDisable = isBackupDisabled(); - HandlerThread handlerThread = - new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - mUserManager = UserManager.get(context); - mUserServices = userServices; - mService = new BackupManagerService(mContext, this, mUserServices); - Set<ComponentName> transportWhitelist = - SystemConfig.getInstance().getBackupTransportWhitelist(); - mTransportWhitelist = (transportWhitelist == null) ? emptySet() : transportWhitelist; - mContext.registerReceiver( - mUserRemovedReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED)); - } - - // TODO: Remove this when we implement DI by injecting in the construtor. - @VisibleForTesting - Handler getBackupHandler() { - return mHandler; - } - - protected boolean isBackupDisabled() { - return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); - } - - protected int binderGetCallingUserId() { - return Binder.getCallingUserHandle().getIdentifier(); - } - - protected int binderGetCallingUid() { - return Binder.getCallingUid(); - } - - /** Stored in the system user's directory. */ - protected File getSuppressFileForSystemUser() { - return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), - BACKUP_SUPPRESS_FILENAME); - } - - /** Stored in the system user's directory and the file is indexed by the user it refers to. */ - protected File getRememberActivatedFileForNonSystemUser(int userId) { - return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId); - } - - /** Stored in the system user's directory and the file is indexed by the user it refers to. */ - protected File getActivatedFileForNonSystemUser(int userId) { - return UserBackupManagerFiles.getStateFileInSystemDir(BACKUP_ACTIVATED_FILENAME, userId); - } - - /** - * Remove backup state for non system {@code userId} when the user is removed from the device. - * For non system users, backup state is stored in both the user's own dir and the system dir. - * When the user is removed, the user's own dir gets removed by the OS. This method ensures that - * the part of the user backup state which is in the system dir also gets removed. - */ - private void onRemovedNonSystemUser(int userId) { - Slog.i(TAG, "Removing state for non system user " + userId); - File dir = UserBackupManagerFiles.getStateDirInSystemDir(userId); - if (!FileUtils.deleteContentsAndDir(dir)) { - Slog.w(TAG, "Failed to delete state dir for removed user: " + userId); - } - } - - // TODO (b/124359804) move to util method in FileUtils - private void createFile(File file) throws IOException { - if (file.exists()) { - return; - } - - file.getParentFile().mkdirs(); - if (!file.createNewFile()) { - Slog.w(TAG, "Failed to create file " + file.getPath()); - } - } - - // TODO (b/124359804) move to util method in FileUtils - private void deleteFile(File file) { - if (!file.exists()) { - return; - } - - if (!file.delete()) { - Slog.w(TAG, "Failed to delete file " + file.getPath()); - } - } - - /** - * Deactivates the backup service for user {@code userId}. If this is the system user, it - * creates a suppress file which disables backup for all users. If this is a non-system user, it - * only deactivates backup for that user by deleting its activate file. - */ - @GuardedBy("mStateLock") - private void deactivateBackupForUserLocked(int userId) throws IOException { - if (userId == UserHandle.USER_SYSTEM) { - createFile(getSuppressFileForSystemUser()); - } else { - deleteFile(getActivatedFileForNonSystemUser(userId)); - } - } - - /** - * Enables the backup service for user {@code userId}. If this is the system user, it deletes - * the suppress file. If this is a non-system user, it creates the user's activate file. Note, - * deleting the suppress file does not automatically enable backup for non-system users, they - * need their own activate file in order to participate in the service. - */ - @GuardedBy("mStateLock") - private void activateBackupForUserLocked(int userId) throws IOException { - if (userId == UserHandle.USER_SYSTEM) { - deleteFile(getSuppressFileForSystemUser()); - } else { - createFile(getActivatedFileForNonSystemUser(userId)); - } - } - - // This method should not perform any I/O (e.g. do not call isBackupActivatedForUser), - // it's used in multiple places where I/O waits would cause system lock-ups. - private boolean isUserReadyForBackup(int userId) { - return mUserServices.get(UserHandle.USER_SYSTEM) != null - && mUserServices.get(userId) != null; - } - - /** - * Backup is activated for the system user if the suppress file does not exist. Backup is - * activated for non-system users if the suppress file does not exist AND the user's activated - * file exists. - */ - private boolean isBackupActivatedForUser(int userId) { - if (getSuppressFileForSystemUser().exists()) { - return false; - } - - return userId == UserHandle.USER_SYSTEM - || getActivatedFileForNonSystemUser(userId).exists(); - } - - protected Context getContext() { - return mContext; - } - - protected UserManager getUserManager() { - return mUserManager; - } - - protected void postToHandler(Runnable runnable) { - mHandler.post(runnable); - } - - /** - * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked. - * Starts the backup service for this user if backup is active for this user. Offloads work onto - * the handler thread {@link #mHandlerThread} to keep unlock time low since backup is not - * essential for device functioning. - */ - void onUnlockUser(int userId) { - postToHandler(() -> startServiceForUser(userId)); - } - - /** - * Starts the backup service for user {@code userId} by creating a new instance of {@link - * UserBackupManagerService} and registering it with this service. - */ - @VisibleForTesting - void startServiceForUser(int userId) { - // We know that the user is unlocked here because it is called from setBackupServiceActive - // and unlockUser which have these guarantees. So we can check if the file exists. - if (mGlobalDisable) { - Slog.i(TAG, "Backup service not supported"); - return; - } - if (!isBackupActivatedForUser(userId)) { - Slog.i(TAG, "Backup not activated for user " + userId); - return; - } - if (mUserServices.get(userId) != null) { - Slog.i(TAG, "userId " + userId + " already started, so not starting again"); - return; - } - Slog.i(TAG, "Starting service for user: " + userId); - UserBackupManagerService userBackupManagerService = - UserBackupManagerService.createAndInitializeService( - userId, mContext, this, mTransportWhitelist); - startServiceForUser(userId, userBackupManagerService); - } - - /** - * Starts the backup service for user {@code userId} by registering its instance of {@link - * UserBackupManagerService} with this service and setting enabled state. - */ - void startServiceForUser(int userId, UserBackupManagerService userBackupManagerService) { - mUserServices.put(userId, userBackupManagerService); - - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable"); - userBackupManagerService.initializeBackupEnableState(); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } - - /** Stops the backup service for user {@code userId} when the user is stopped. */ - @VisibleForTesting - protected void stopServiceForUser(int userId) { - UserBackupManagerService userBackupManagerService = mUserServices.removeReturnOld(userId); - - if (userBackupManagerService != null) { - userBackupManagerService.tearDownService(); - - KeyValueBackupJob.cancel(userId, mContext); - FullBackupJob.cancel(userId, mContext); - } - } - - /** - * Returns a list of users currently unlocked that have a {@link UserBackupManagerService} - * registered. - * - * Warning: Do NOT modify returned object as it's used inside. - * - * TODO: Return a copy or only expose read-only information through other means. - */ - @VisibleForTesting - SparseArray<UserBackupManagerService> getUserServices() { - return mUserServices; - } - - /** - * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is stopped. - * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low. - */ - void onStopUser(int userId) { - postToHandler( - () -> { - if (!mGlobalDisable) { - Slog.i(TAG, "Stopping service for user: " + userId); - stopServiceForUser(userId); - } - }); - } - - /** Returns {@link UserBackupManagerService} for user {@code userId}. */ - @Nullable - public UserBackupManagerService getUserService(int userId) { - return mUserServices.get(userId); - } - - /** - * The system user and managed profiles can only be acted on by callers in the system or root - * processes. Other users can be acted on by callers who have both android.permission.BACKUP and - * android.permission.INTERACT_ACROSS_USERS_FULL permissions. - */ - private void enforcePermissionsOnUser(int userId) throws SecurityException { - boolean isRestrictedUser = - userId == UserHandle.USER_SYSTEM - || getUserManager().getUserInfo(userId).isManagedProfile(); - - if (isRestrictedUser) { - int caller = binderGetCallingUid(); - if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { - throw new SecurityException("No permission to configure backup activity"); - } - } else { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.BACKUP, "No permission to configure backup activity"); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "No permission to configure backup activity"); - } - } - - /** - * Only privileged callers should be changing the backup state. Deactivating backup in the - * system user also deactivates backup in all users. We are not guaranteed that {@code userId} - * is unlocked at this point yet, so handle both cases. - */ - public void setBackupServiceActive(int userId, boolean makeActive) { - enforcePermissionsOnUser(userId); - - // In Q, backup is OFF by default for non-system users. In the future, we will change that - // to ON unless backup was explicitly deactivated with a (permissioned) call to - // setBackupServiceActive. - // Therefore, remember this for use in the future. Basically the default in the future will - // be: rememberFile.exists() ? rememberFile.value() : ON - // Note that this has to be done right after the permission checks and before any other - // action since we need to remember that a permissioned call was made irrespective of - // whether the call changes the state or not. - if (userId != UserHandle.USER_SYSTEM) { - try { - File rememberFile = getRememberActivatedFileForNonSystemUser(userId); - createFile(rememberFile); - RandomAccessFileUtils.writeBoolean(rememberFile, makeActive); - } catch (IOException e) { - Slog.e(TAG, "Unable to persist backup service activity", e); - } - } - - if (mGlobalDisable) { - Slog.i(TAG, "Backup service not supported"); - return; - } - - synchronized (mStateLock) { - Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active"); - if (makeActive) { - try { - activateBackupForUserLocked(userId); - } catch (IOException e) { - Slog.e(TAG, "Unable to persist backup service activity"); - } - - // If the user is unlocked, we can start the backup service for it. Otherwise we - // will start the service when the user is unlocked as part of its unlock callback. - if (getUserManager().isUserUnlocked(userId)) { - // Clear calling identity as initialization enforces the system identity but we - // can be coming from shell. - long oldId = Binder.clearCallingIdentity(); - try { - startServiceForUser(userId); - } finally { - Binder.restoreCallingIdentity(oldId); - } - } - } else { - try { - //TODO(b/121198006): what if this throws an exception? - deactivateBackupForUserLocked(userId); - } catch (IOException e) { - Slog.e(TAG, "Unable to persist backup service inactivity"); - } - //TODO(b/121198006): loop through active users that have work profile and - // stop them as well. - onStopUser(userId); - } - } - } - - // IBackupManager binder API - - /** - * Querying activity state of backup service. - * - * @param userId The user in which the activity state of backup service is queried. - * @return true if the service is active. - */ - @Override - public boolean isBackupServiceActive(int userId) { - synchronized (mStateLock) { - return !mGlobalDisable && isBackupActivatedForUser(userId); - } - } - - @Override - public void dataChangedForUser(int userId, String packageName) throws RemoteException { - if (isUserReadyForBackup(userId)) { - dataChanged(userId, packageName); - } - } - - @Override - public void dataChanged(String packageName) throws RemoteException { - dataChangedForUser(binderGetCallingUserId(), packageName); - } - - /** - * An app's backup agent calls this method to let the service know that there's new data to - * backup for their app {@code packageName}. Only used for apps participating in key-value - * backup. - */ - public void dataChanged(@UserIdInt int userId, String packageName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "dataChanged()"); - - if (userBackupManagerService != null) { - userBackupManagerService.dataChanged(packageName); - } - } - - // --------------------------------------------- - // TRANSPORT OPERATIONS - // --------------------------------------------- - - @Override - public void initializeTransportsForUser( - int userId, String[] transportNames, IBackupObserver observer) throws RemoteException { - if (isUserReadyForBackup(userId)) { - initializeTransports(userId, transportNames, observer); - } - } - - /** Run an initialize operation for the given transports {@code transportNames}. */ - public void initializeTransports( - @UserIdInt int userId, String[] transportNames, IBackupObserver observer) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "initializeTransports()"); - - if (userBackupManagerService != null) { - userBackupManagerService.initializeTransports(transportNames, observer); - } - } - - @Override - public void clearBackupDataForUser(int userId, String transportName, String packageName) - throws RemoteException { - if (isUserReadyForBackup(userId)) { - clearBackupData(userId, transportName, packageName); - } - } - - /** - * Clear the given package {@code packageName}'s backup data from the transport {@code - * transportName}. - */ - public void clearBackupData(@UserIdInt int userId, String transportName, String packageName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "clearBackupData()"); - - if (userBackupManagerService != null) { - userBackupManagerService.clearBackupData(transportName, packageName); - } - } - - @Override - public void clearBackupData(String transportName, String packageName) - throws RemoteException { - clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName); - } - - @Override - public void agentConnectedForUser(int userId, String packageName, IBinder agent) - throws RemoteException { - if (isUserReadyForBackup(userId)) { - agentConnected(userId, packageName, agent); - } - } - - @Override - public void agentConnected(String packageName, IBinder agent) throws RemoteException { - agentConnectedForUser(binderGetCallingUserId(), packageName, agent); - } - - /** - * Callback: a requested backup agent has been instantiated. This should only be called from the - * {@link ActivityManager}. - */ - public void agentConnected(@UserIdInt int userId, String packageName, IBinder agentBinder) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "agentConnected()"); - - if (userBackupManagerService != null) { - userBackupManagerService.agentConnected(packageName, agentBinder); - } - } - - @Override - public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException { - if (isUserReadyForBackup(userId)) { - agentDisconnected(userId, packageName); - } - } - - @Override - public void agentDisconnected(String packageName) throws RemoteException { - agentDisconnectedForUser(binderGetCallingUserId(), packageName); - } - - /** - * Callback: a backup agent has failed to come up, or has unexpectedly quit. This should only be - * called from the {@link ActivityManager}. - */ - public void agentDisconnected(@UserIdInt int userId, String packageName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()"); - - if (userBackupManagerService != null) { - userBackupManagerService.agentDisconnected(packageName); - } - } - - @Override - public void restoreAtInstallForUser(int userId, String packageName, int token) - throws RemoteException { - if (isUserReadyForBackup(userId)) { - restoreAtInstall(userId, packageName, token); - } - } - - @Override - public void restoreAtInstall(String packageName, int token) throws RemoteException { - restoreAtInstallForUser(binderGetCallingUserId(), packageName, token); - } - - /** - * Used to run a restore pass for an application that is being installed. This should only be - * called from the {@link PackageManager}. - */ - public void restoreAtInstall(@UserIdInt int userId, String packageName, int token) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "restoreAtInstall()"); - - if (userBackupManagerService != null) { - userBackupManagerService.restoreAtInstall(packageName, token); - } - } - - @Override - public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled) - throws RemoteException { - if (isUserReadyForBackup(userId)) { - setBackupEnabled(userId, isEnabled); - } - } - - @Override - public void setBackupEnabled(boolean isEnabled) throws RemoteException { - setBackupEnabledForUser(binderGetCallingUserId(), isEnabled); - } - - /** Enable/disable the backup service. This is user-configurable via backup settings. */ - public void setBackupEnabled(@UserIdInt int userId, boolean enable) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "setBackupEnabled()"); - - if (userBackupManagerService != null) { - userBackupManagerService.setBackupEnabled(enable); - } - } - - @Override - public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException { - if (isUserReadyForBackup(userId)) { - setAutoRestore(userId, doAutoRestore); - } - } - - @Override - public void setAutoRestore(boolean doAutoRestore) throws RemoteException { - setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore); - } - - /** Enable/disable automatic restore of app data at install time. */ - public void setAutoRestore(@UserIdInt int userId, boolean autoRestore) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "setAutoRestore()"); - - if (userBackupManagerService != null) { - userBackupManagerService.setAutoRestore(autoRestore); - } - } - - @Override - public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException { - return isUserReadyForBackup(userId) && isBackupEnabled(userId); - } - - @Override - public boolean isBackupEnabled() throws RemoteException { - return isBackupEnabledForUser(binderGetCallingUserId()); - } - - /** - * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}. - */ - public boolean isBackupEnabled(@UserIdInt int userId) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "isBackupEnabled()"); - - return userBackupManagerService != null && userBackupManagerService.isBackupEnabled(); - } - - /** Sets the backup password used when running adb backup. */ - @Override - public boolean setBackupPassword(String currentPassword, String newPassword) { - int userId = binderGetCallingUserId(); - if (!isUserReadyForBackup(userId)) { - return false; - } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission( - UserHandle.USER_SYSTEM, "setBackupPassword()"); - - return userBackupManagerService != null - && userBackupManagerService.setBackupPassword(currentPassword, newPassword); - } - - /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */ - @Override - public boolean hasBackupPassword() throws RemoteException { - int userId = binderGetCallingUserId(); - if (!isUserReadyForBackup(userId)) { - return false; - } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission( - UserHandle.USER_SYSTEM, "hasBackupPassword()"); - - return userBackupManagerService != null && userBackupManagerService.hasBackupPassword(); - } - - @Override - public void backupNowForUser(@UserIdInt int userId) throws RemoteException { - if (isUserReadyForBackup(userId)) { - backupNow(userId); - } - } - - @Override - public void backupNow() throws RemoteException { - backupNowForUser(binderGetCallingUserId()); - } - - /** - * Run a backup pass immediately for any key-value backup applications that have declared that - * they have pending updates. - */ - public void backupNow(@UserIdInt int userId) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "backupNow()"); - - if (userBackupManagerService != null) { - userBackupManagerService.backupNow(); - } - } - - /** - * Used by 'adb backup' to run a backup pass for packages {@code packageNames} supplied via the - * command line, writing the resulting data stream to the supplied {@code fd}. This method is - * synchronous and does not return to the caller until the backup has been completed. It - * requires on-screen confirmation by the user. - */ - @Override - public void adbBackup( - @UserIdInt int userId, - ParcelFileDescriptor fd, - boolean includeApks, - boolean includeObbs, - boolean includeShared, - boolean doWidgets, - boolean doAllApps, - boolean includeSystem, - boolean doCompress, - boolean doKeyValue, - String[] packageNames) { - if (!isUserReadyForBackup(userId)) { - return; - } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "adbBackup()"); - - if (userBackupManagerService != null) { - userBackupManagerService.adbBackup( - fd, - includeApks, - includeObbs, - includeShared, - doWidgets, - doAllApps, - includeSystem, - doCompress, - doKeyValue, - packageNames); - } - } - - @Override - public void fullTransportBackupForUser(int userId, String[] packageNames) - throws RemoteException { - if (isUserReadyForBackup(userId)) { - fullTransportBackup(userId, packageNames); - } - } - - /** - * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'. - */ - public void fullTransportBackup(@UserIdInt int userId, String[] packageNames) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "fullTransportBackup()"); - - if (userBackupManagerService != null) { - userBackupManagerService.fullTransportBackup(packageNames); - } - } - - /** - * Used by 'adb restore' to run a restore pass reading from the supplied {@code fd}. This method - * is synchronous and does not return to the caller until the restore has been completed. It - * requires on-screen confirmation by the user. - */ - @Override - public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) { - if (!isUserReadyForBackup(userId)) { - return; - } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "adbRestore()"); - - if (userBackupManagerService != null) { - userBackupManagerService.adbRestore(fd); - } - } - - @Override - public void acknowledgeFullBackupOrRestoreForUser( - int userId, - int token, - boolean allow, - String curPassword, - String encryptionPassword, - IFullBackupRestoreObserver observer) - throws RemoteException { - if (isUserReadyForBackup(userId)) { - acknowledgeAdbBackupOrRestore(userId, token, allow, - curPassword, encryptionPassword, observer); - } - } - - /** - * Confirm that the previously requested adb backup/restore operation can proceed. This is used - * to require a user-facing disclosure about the operation. - */ - public void acknowledgeAdbBackupOrRestore( - @UserIdInt int userId, - int token, - boolean allow, - String currentPassword, - String encryptionPassword, - IFullBackupRestoreObserver observer) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "acknowledgeAdbBackupOrRestore()"); - - if (userBackupManagerService != null) { - userBackupManagerService.acknowledgeAdbBackupOrRestore( - token, allow, currentPassword, encryptionPassword, observer); - } - } - - @Override - public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, - String encryptionPassword, IFullBackupRestoreObserver observer) - throws RemoteException { - acknowledgeFullBackupOrRestoreForUser( - binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer); - } - - - @Override - public String getCurrentTransportForUser(int userId) throws RemoteException { - return (isUserReadyForBackup(userId)) ? getCurrentTransport(userId) : null; - } - - @Override - public String getCurrentTransport() throws RemoteException { - return getCurrentTransportForUser(binderGetCallingUserId()); - } - - /** Return the name of the currently active transport. */ - @Nullable - public String getCurrentTransport(@UserIdInt int userId) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "getCurrentTransport()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.getCurrentTransport(); - } - - /** - * Returns the {@link ComponentName} of the host service of the selected transport or - * {@code null} if no transport selected or if the transport selected is not registered. - */ - @Override - @Nullable - public ComponentName getCurrentTransportComponentForUser(int userId) { - return (isUserReadyForBackup(userId)) ? getCurrentTransportComponent(userId) : null; - } - - /** - * Returns the {@link ComponentName} of the host service of the selected transport or {@code - * null} if no transport selected or if the transport selected is not registered. - */ - @Nullable - public ComponentName getCurrentTransportComponent(@UserIdInt int userId) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "getCurrentTransportComponent()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.getCurrentTransportComponent(); - } - - @Override - public String[] listAllTransportsForUser(int userId) throws RemoteException { - return (isUserReadyForBackup(userId)) ? listAllTransports(userId) : null; - } - - /** Report all known, available backup transports by name. */ - @Nullable - public String[] listAllTransports(@UserIdInt int userId) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "listAllTransports()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.listAllTransports(); - } - - @Override - public String[] listAllTransports() throws RemoteException { - return listAllTransportsForUser(binderGetCallingUserId()); - } - - @Override - public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException { - return (isUserReadyForBackup(userId)) - ? listAllTransportComponents(userId) : null; - } - - /** Report all known, available backup transports by {@link ComponentName}. */ - @Nullable - public ComponentName[] listAllTransportComponents(@UserIdInt int userId) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "listAllTransportComponents()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.listAllTransportComponents(); - } - - @Override - public String[] getTransportWhitelist() { - int userId = binderGetCallingUserId(); - if (!isUserReadyForBackup(userId)) { - return null; - } - // No permission check, intentionally. - String[] whitelistedTransports = new String[mTransportWhitelist.size()]; - int i = 0; - for (ComponentName component : mTransportWhitelist) { - whitelistedTransports[i] = component.flattenToShortString(); - i++; - } - return whitelistedTransports; - } - - @Override - public void updateTransportAttributesForUser( - int userId, - ComponentName transportComponent, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - CharSequence dataManagementLabel) { - if (isUserReadyForBackup(userId)) { - updateTransportAttributes( - userId, - transportComponent, - name, - configurationIntent, - currentDestinationString, - dataManagementIntent, - dataManagementLabel); - } - } - - /** - * Update the attributes of the transport identified by {@code transportComponent}. If the - * specified transport has not been bound at least once (for registration), this call will be - * ignored. Only the host process of the transport can change its description, otherwise a - * {@link SecurityException} will be thrown. - * - * @param transportComponent The identity of the transport being described. - * @param name A {@link String} with the new name for the transport. This is NOT for - * identification. MUST NOT be {@code null}. - * @param configurationIntent An {@link Intent} that can be passed to {@link - * Context#startActivity} in order to launch the transport's configuration UI. It may be - * {@code null} if the transport does not offer any user-facing configuration UI. - * @param currentDestinationString A {@link String} describing the destination to which the - * transport is currently sending data. MUST NOT be {@code null}. - * @param dataManagementIntent An {@link Intent} that can be passed to {@link - * Context#startActivity} in order to launch the transport's data-management UI. It may be - * {@code null} if the transport does not offer any user-facing data management UI. - * @param dataManagementLabel A {@link CharSequence} to be used as the label for the transport's - * data management affordance. This MUST be {@code null} when dataManagementIntent is {@code - * null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}. - * @throws SecurityException If the UID of the calling process differs from the package UID of - * {@code transportComponent} or if the caller does NOT have BACKUP permission. - */ - public void updateTransportAttributes( - @UserIdInt int userId, - ComponentName transportComponent, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - CharSequence dataManagementLabel) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "updateTransportAttributes()"); - - if (userBackupManagerService != null) { - userBackupManagerService.updateTransportAttributes( - transportComponent, - name, - configurationIntent, - currentDestinationString, - dataManagementIntent, - dataManagementLabel); - } - } - - @Override - public String selectBackupTransportForUser(int userId, String transport) - throws RemoteException { - return (isUserReadyForBackup(userId)) - ? selectBackupTransport(userId, transport) : null; - } - - @Override - public String selectBackupTransport(String transport) throws RemoteException { - return selectBackupTransportForUser(binderGetCallingUserId(), transport); - } - - /** - * Selects transport {@code transportName} and returns the previously selected transport. - * - * @deprecated Use {@link #selectBackupTransportAsync(ComponentName, - * ISelectBackupTransportCallback)} instead. - */ - @Deprecated - @Nullable - public String selectBackupTransport(@UserIdInt int userId, String transportName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "selectBackupTransport()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.selectBackupTransport(transportName); - } - - @Override - public void selectBackupTransportAsyncForUser(int userId, ComponentName transport, - ISelectBackupTransportCallback listener) throws RemoteException { - if (isUserReadyForBackup(userId)) { - selectBackupTransportAsync(userId, transport, listener); - } else { - if (listener != null) { - try { - listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED); - } catch (RemoteException ex) { - // ignore - } - } - } - } - - /** - * Selects transport {@code transportComponent} asynchronously and notifies {@code listener} - * with the result upon completion. - */ - public void selectBackupTransportAsync( - @UserIdInt int userId, - ComponentName transportComponent, - ISelectBackupTransportCallback listener) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "selectBackupTransportAsync()"); - - if (userBackupManagerService != null) { - userBackupManagerService.selectBackupTransportAsync(transportComponent, listener); - } - } - - @Override - public Intent getConfigurationIntentForUser(int userId, String transport) - throws RemoteException { - return isUserReadyForBackup(userId) ? getConfigurationIntent(userId, transport) - : null; - } - - @Override - public Intent getConfigurationIntent(String transport) - throws RemoteException { - return getConfigurationIntentForUser(binderGetCallingUserId(), transport); - } - - /** - * Supply the configuration intent for the given transport. If the name is not one of the - * available transports, or if the transport does not supply any configuration UI, the method - * returns {@code null}. - */ - @Nullable - public Intent getConfigurationIntent(@UserIdInt int userId, String transportName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "getConfigurationIntent()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.getConfigurationIntent(transportName); - } - - @Override - public String getDestinationStringForUser(int userId, String transport) throws RemoteException { - return isUserReadyForBackup(userId) ? getDestinationString(userId, transport) - : null; - } - - @Override - public String getDestinationString(String transport) throws RemoteException { - return getDestinationStringForUser(binderGetCallingUserId(), transport); - } - - /** - * Supply the current destination string for the given transport. If the name is not one of the - * registered transports the method will return null. - * - * <p>This string is used VERBATIM as the summary text of the relevant Settings item. - * - * @param transportName The name of the registered transport. - * @return The current destination string or null if the transport is not registered. - */ - @Nullable - public String getDestinationString(@UserIdInt int userId, String transportName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "getDestinationString()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.getDestinationString(transportName); - } - - @Override - public Intent getDataManagementIntentForUser(int userId, String transport) - throws RemoteException { - return isUserReadyForBackup(userId) - ? getDataManagementIntent(userId, transport) : null; - } - - @Override - public Intent getDataManagementIntent(String transport) - throws RemoteException { - return getDataManagementIntentForUser(binderGetCallingUserId(), transport); - } - - /** Supply the manage-data intent for the given transport. */ - @Nullable - public Intent getDataManagementIntent(@UserIdInt int userId, String transportName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "getDataManagementIntent()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.getDataManagementIntent(transportName); - } - - @Override - public CharSequence getDataManagementLabelForUser(int userId, String transport) - throws RemoteException { - return isUserReadyForBackup(userId) ? getDataManagementLabel(userId, transport) - : null; - } - - /** - * Supply the menu label for affordances that fire the manage-data intent for the given - * transport. - */ - @Nullable - public CharSequence getDataManagementLabel(@UserIdInt int userId, String transportName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "getDataManagementLabel()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.getDataManagementLabel(transportName); - } - - @Override - public IRestoreSession beginRestoreSessionForUser( - int userId, String packageName, String transportID) throws RemoteException { - return isUserReadyForBackup(userId) - ? beginRestoreSession(userId, packageName, transportID) : null; - } - - /** - * Begin a restore for the specified package {@code packageName} using the specified transport - * {@code transportName}. - */ - @Nullable - public IRestoreSession beginRestoreSession( - @UserIdInt int userId, String packageName, String transportName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.beginRestoreSession(packageName, transportName); - } - - @Override - public void opCompleteForUser(int userId, int token, long result) throws RemoteException { - if (isUserReadyForBackup(userId)) { - opComplete(userId, token, result); - } - } - - @Override - public void opComplete(int token, long result) throws RemoteException { - opCompleteForUser(binderGetCallingUserId(), token, result); - } - - /** - * Used by a currently-active backup agent to notify the service that it has completed its given - * outstanding asynchronous backup/restore operation. - */ - public void opComplete(@UserIdInt int userId, int token, long result) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "opComplete()"); - - if (userBackupManagerService != null) { - userBackupManagerService.opComplete(token, result); - } - } - - @Override - public long getAvailableRestoreTokenForUser(int userId, String packageName) { - return isUserReadyForBackup(userId) ? getAvailableRestoreToken(userId, packageName) : 0; - } - - /** - * Get the restore-set token for the best-available restore set for this {@code packageName}: - * the active set if possible, else the ancestral one. Returns zero if none available. - */ - public long getAvailableRestoreToken(@UserIdInt int userId, String packageName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "getAvailableRestoreToken()"); - - return userBackupManagerService == null - ? 0 - : userBackupManagerService.getAvailableRestoreToken(packageName); - } - - @Override - public boolean isAppEligibleForBackupForUser(int userId, String packageName) { - return isUserReadyForBackup(userId) && isAppEligibleForBackup(userId, - packageName); - } - - /** Checks if the given package {@code packageName} is eligible for backup. */ - public boolean isAppEligibleForBackup(@UserIdInt int userId, String packageName) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "isAppEligibleForBackup()"); - - return userBackupManagerService != null - && userBackupManagerService.isAppEligibleForBackup(packageName); - } - - @Override - public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) { - return isUserReadyForBackup(userId) ? filterAppsEligibleForBackup(userId, packages) : null; - } - - /** - * Returns from the inputted packages {@code packages}, the ones that are eligible for backup. - */ - @Nullable - public String[] filterAppsEligibleForBackup(@UserIdInt int userId, String[] packages) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "filterAppsEligibleForBackup()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.filterAppsEligibleForBackup(packages); - } - - @Override - public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver - observer, IBackupManagerMonitor monitor, int flags) throws RemoteException { - if (!isUserReadyForBackup(userId)) { - return BackupManager.ERROR_BACKUP_NOT_ALLOWED; - } - return requestBackup(userId, packages, observer, monitor, flags); - } - - @Override - public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags) throws RemoteException { - return requestBackupForUser(binderGetCallingUserId(), packages, - observer, monitor, flags); - } - - /** - * Requests a backup for the inputted {@code packages} with a specified callback {@link - * IBackupManagerMonitor} for receiving events during the operation. - */ - public int requestBackup( - @UserIdInt int userId, - String[] packages, - IBackupObserver observer, - IBackupManagerMonitor monitor, - int flags) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "requestBackup()"); - - return userBackupManagerService == null - ? BackupManager.ERROR_BACKUP_NOT_ALLOWED - : userBackupManagerService.requestBackup(packages, observer, monitor, flags); - } - - @Override - public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException { - if (isUserReadyForBackup(userId)) { - cancelBackups(userId); - } - } - - @Override - public void cancelBackups() throws RemoteException { - cancelBackupsForUser(binderGetCallingUserId()); - } - - /** Cancel all running backup operations. */ - public void cancelBackups(@UserIdInt int userId) { - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "cancelBackups()"); - - if (userBackupManagerService != null) { - userBackupManagerService.cancelBackups(); - } - } - - /** - * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the - * serial number of its ancestral work profile or null if there is no {@link - * UserBackupManagerService} associated with that user. - * - * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)} - * and it corresponds to the profile that was used to restore to the callers profile. - */ - @Override - @Nullable - public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) { - if (mGlobalDisable) { - return null; - } - int callingUserId = Binder.getCallingUserHandle().getIdentifier(); - long oldId = Binder.clearCallingIdentity(); - final int[] userIds; - try { - userIds = - mContext - .getSystemService(UserManager.class) - .getProfileIds(callingUserId, false); - } finally { - Binder.restoreCallingIdentity(oldId); - } - - for (int userId : userIds) { - UserBackupManagerService userBackupManagerService = mUserServices.get(userId); - if (userBackupManagerService != null) { - if (userBackupManagerService.getAncestralSerialNumber() == ancestralSerialNumber) { - return UserHandle.of(userId); - } - } - } - - return null; - } - - /** - * Sets the ancestral work profile for the calling user. - * - * <p> The ancestral work profile corresponds to the profile that was used to restore to the - * callers profile. - */ - @Override - public void setAncestralSerialNumber(long ancestralSerialNumber) { - if (mGlobalDisable) { - return; - } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission( - Binder.getCallingUserHandle().getIdentifier(), - "setAncestralSerialNumber()"); - - if (userBackupManagerService != null) { - userBackupManagerService.setAncestralSerialNumber(ancestralSerialNumber); - } - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { - return; - } - int userId = binderGetCallingUserId(); - if (!isUserReadyForBackup(userId)) { - pw.println("Inactive"); - return; - } - - if (args != null) { - for (String arg : args) { - if ("users".equals(arg.toLowerCase())) { - pw.print(DUMP_RUNNING_USERS_MESSAGE); - for (int i = 0; i < mUserServices.size(); i++) { - pw.print(" " + mUserServices.keyAt(i)); - } - pw.println(); - return; - } - } - } - - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()"); - - if (userBackupManagerService != null) { - userBackupManagerService.dump(fd, pw, args); - } - } - - /** - * Used by the {@link JobScheduler} to run a full backup when conditions are right. The model we - * use is to perform one app backup per scheduled job execution, and to reschedule the job with - * zero latency as long as conditions remain right and we still have work to do. - * - * @return Whether ongoing work will continue. The return value here will be passed along as the - * return value to the callback {@link JobService#onStartJob(JobParameters)}. - */ - public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) { - if (!isUserReadyForBackup(userId)) { - return false; - } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()"); - - return userBackupManagerService != null - && userBackupManagerService.beginFullBackup(scheduledJob); - } - - /** - * Used by the {@link JobScheduler} to end the current full backup task when conditions are no - * longer met for running the full backup job. - */ - public void endFullBackup(@UserIdInt int userId) { - if (!isUserReadyForBackup(userId)) { - return; - } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(userId, "endFullBackup()"); - - if (userBackupManagerService != null) { - userBackupManagerService.endFullBackup(); - } - } - - /** - * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}. - * If the user is not registered with the service (either the user is locked or not eligible for - * the backup service) then return {@code null}. - * - * @param userId The id of the user to retrieve its instance of {@link - * UserBackupManagerService}. - * @param caller A {@link String} identifying the caller for logging purposes. - * @throws SecurityException if {@code userId} is different from the calling user id and the - * caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission. - */ - @Nullable - @VisibleForTesting - UserBackupManagerService getServiceForUserIfCallerHasPermission( - @UserIdInt int userId, String caller) { - enforceCallingPermissionOnUserId(userId, caller); - UserBackupManagerService userBackupManagerService = mUserServices.get(userId); - if (userBackupManagerService == null) { - Slog.w(TAG, "Called " + caller + " for unknown user: " + userId); - } - return userBackupManagerService; - } - - /** - * If {@code userId} is different from the calling user id, then the caller must hold the - * android.permission.INTERACT_ACROSS_USERS_FULL permission. - * - * @param userId User id on which the backup operation is being requested. - * @param message A message to include in the exception if it is thrown. - */ - void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) { - if (Binder.getCallingUserHandle().getIdentifier() != userId) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - } - } -} diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 0e81e077652e..77888db416a5 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -421,13 +421,13 @@ public class UserBackupManagerService { * Creates an instance of {@link UserBackupManagerService} and initializes state for it. This * includes setting up the directories where we keep our bookkeeping and transport management. * - * @see #createAndInitializeService(int, Context, Trampoline, HandlerThread, File, File, - * TransportManager) + * @see #createAndInitializeService(int, Context, BackupManagerService, HandlerThread, File, + * File, TransportManager) */ static UserBackupManagerService createAndInitializeService( @UserIdInt int userId, Context context, - Trampoline trampoline, + BackupManagerService backupManagerService, Set<ComponentName> transportWhitelist) { String currentTransport = Settings.Secure.getStringForUser( @@ -455,7 +455,7 @@ public class UserBackupManagerService { return createAndInitializeService( userId, context, - trampoline, + backupManagerService, userBackupThread, baseStateDir, dataDir, @@ -467,7 +467,7 @@ public class UserBackupManagerService { * * @param userId The user which this service is for. * @param context The system server context. - * @param trampoline A reference to the proxy to {@link BackupManagerService}. + * @param backupManagerService A reference to the proxy to {@link BackupManagerService}. * @param userBackupThread The thread running backup/restore operations for the user. * @param baseStateDir The directory we store the user's persistent bookkeeping data. * @param dataDir The directory we store the user's temporary staging data. @@ -478,7 +478,7 @@ public class UserBackupManagerService { public static UserBackupManagerService createAndInitializeService( @UserIdInt int userId, Context context, - Trampoline trampoline, + BackupManagerService backupManagerService, HandlerThread userBackupThread, File baseStateDir, File dataDir, @@ -486,7 +486,7 @@ public class UserBackupManagerService { return new UserBackupManagerService( userId, context, - trampoline, + backupManagerService, userBackupThread, baseStateDir, dataDir, @@ -509,7 +509,7 @@ public class UserBackupManagerService { private UserBackupManagerService( @UserIdInt int userId, Context context, - Trampoline parent, + BackupManagerService parent, HandlerThread userBackupThread, File baseStateDir, File dataDir, @@ -525,8 +525,8 @@ public class UserBackupManagerService { mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); - checkNotNull(parent, "trampoline cannot be null"); - mBackupManagerBinder = Trampoline.asInterface(parent.asBinder()); + checkNotNull(parent, "parent cannot be null"); + mBackupManagerBinder = BackupManagerService.asInterface(parent.asBinder()); mAgentTimeoutParameters = new BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver()); diff --git a/services/core/java/com/android/server/AnimationThread.java b/services/core/java/com/android/server/AnimationThread.java index c86042b23460..c607b1e058bc 100644 --- a/services/core/java/com/android/server/AnimationThread.java +++ b/services/core/java/com/android/server/AnimationThread.java @@ -21,6 +21,8 @@ import static android.os.Process.THREAD_PRIORITY_DISPLAY; import android.os.Handler; import android.os.Trace; +import com.android.internal.annotations.VisibleForTesting; + /** * Thread for handling all legacy window animations, or anything that's directly impacting * animations like starting windows or traversals. @@ -55,4 +57,20 @@ public final class AnimationThread extends ServiceThread { return sHandler; } } + + /** + * Disposes current animation thread if it's initialized. Should only be used in tests to set up + * a new environment. + */ + @VisibleForTesting + public static void dispose() { + synchronized (DisplayThread.class) { + if (sInstance == null) { + return; + } + + getHandler().runWithScissors(() -> sInstance.quit(), 0 /* timeout */); + sInstance = null; + } + } } diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 6a9f5b651016..d18b4f64f039 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -1235,14 +1235,21 @@ public final class BatteryService extends SystemService { } @Override public void scheduleUpdate() throws RemoteException { - traceBegin("HealthScheduleUpdate"); - try { - IHealth service = mHealthServiceWrapper.getLastService(); - if (service == null) throw new RemoteException("no health service"); - service.update(); - } finally { - traceEnd(); - } + mHealthServiceWrapper.getHandlerThread().getThreadHandler().post(() -> { + traceBegin("HealthScheduleUpdate"); + try { + IHealth service = mHealthServiceWrapper.getLastService(); + if (service == null) { + Slog.e(TAG, "no health service"); + return; + } + service.update(); + } catch (RemoteException ex) { + Slog.e(TAG, "Cannot call update on health HAL", ex); + } finally { + traceEnd(); + } + }); } } @@ -1319,7 +1326,7 @@ public final class BatteryService extends SystemService { Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD); private final IServiceNotification mNotification = new Notification(); - private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh"); + private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder"); // These variables are fixed after init. private Callback mCallback; private IHealthSupplier mHealthSupplier; diff --git a/services/core/java/com/android/server/DisplayThread.java b/services/core/java/com/android/server/DisplayThread.java index 85c799cad321..a07ade050e92 100644 --- a/services/core/java/com/android/server/DisplayThread.java +++ b/services/core/java/com/android/server/DisplayThread.java @@ -20,6 +20,8 @@ import android.os.Handler; import android.os.Process; import android.os.Trace; +import com.android.internal.annotations.VisibleForTesting; + /** * Shared singleton foreground thread for the system. This is a thread for * operations that affect what's on the display, which needs to have a minimum @@ -58,4 +60,20 @@ public final class DisplayThread extends ServiceThread { return sHandler; } } + + /** + * Disposes current display thread if it's initialized. Should only be used in tests to set up a + * new environment. + */ + @VisibleForTesting + public static void dispose() { + synchronized (DisplayThread.class) { + if (sInstance == null) { + return; + } + + getHandler().runWithScissors(() -> sInstance.quit(), 0 /* timeout */); + sInstance = null; + } + } } diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index e53141229e91..18009e191482 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -25,6 +25,7 @@ import android.gsi.IGsid; import android.os.Environment; import android.os.IBinder; import android.os.IBinder.DeathRecipient; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -195,7 +196,20 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements } @Override - public boolean write(byte[] buf) throws RemoteException { - return getGsiService().commitGsiChunkFromMemory(buf); + public boolean setAshmem(ParcelFileDescriptor ashmem, long size) { + try { + return getGsiService().setGsiAshmem(ashmem, size); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } + } + + @Override + public boolean submitFromAshmem(long size) { + try { + return getGsiService().commitGsiChunkFromAshmem(size); + } catch (RemoteException e) { + throw new RuntimeException(e.toString()); + } } } diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index ebe23f61cb2a..b085946899bd 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -212,129 +212,67 @@ public class SystemServiceManager { * Starts the given user. */ public void startUser(final @NonNull TimingsTraceAndSlog t, final @UserIdInt int userHandle) { - t.traceBegin("ssm.startUser-" + userHandle); - Slog.i(TAG, "Calling onStartUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onStartUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onStartUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting start of user " + userHandle - + " to service " + service.getClass().getName(), ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStartUser "); - t.traceEnd(); - } - t.traceEnd(); + onUser(t, "Start", userHandle, (s, u) -> s.onStartUser(u)); } /** * Unlocks the given user. */ public void unlockUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.unlockUser-" + userHandle); - Slog.i(TAG, "Calling onUnlockUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onUnlockUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onUnlockUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting unlock of user " + userHandle - + " to service " + serviceName, ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onUnlockUser "); - t.traceEnd(); - } - t.traceEnd(); + onUser("Unlock", userHandle, (s, u) -> s.onUnlockUser(u)); } /** * Switches to the given user. */ public void switchUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.switchUser-" + userHandle); - Slog.i(TAG, "Calling switchUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onSwitchUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onSwitchUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting switch of user " + userHandle - + " to service " + serviceName, ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onSwitchUser"); - t.traceEnd(); - } - t.traceEnd(); + onUser("Switch", userHandle, (s, u) -> s.onSwitchUser(u)); } /** * Stops the given user. */ public void stopUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.stopUser-" + userHandle); - Slog.i(TAG, "Calling onStopUser u" + userHandle); - final UserInfo userInfo = getUserInfo(userHandle); - final int serviceLen = mServices.size(); - for (int i = 0; i < serviceLen; i++) { - final SystemService service = mServices.get(i); - final String serviceName = service.getClass().getName(); - t.traceBegin("onStopUser-" + userHandle + " " + serviceName); - long time = SystemClock.elapsedRealtime(); - try { - service.onStopUser(userInfo); - } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting stop of user " + userHandle - + " to service " + serviceName, ex); - } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStopUser"); - t.traceEnd(); - } - t.traceEnd(); + onUser("Stop", userHandle, (s, u) -> s.onStopUser(u)); } /** * Cleans up the given user. */ public void cleanupUser(final @UserIdInt int userHandle) { - final TimingsTraceAndSlog t = TimingsTraceAndSlog.newAsyncLog(); - t.traceBegin("ssm.cleanupUser-" + userHandle); - Slog.i(TAG, "Calling onCleanupUser u" + userHandle); + onUser("Cleanup", userHandle, (s, u) -> s.onCleanupUser(u)); + } + + private interface ServiceVisitor { + void visit(@NonNull SystemService service, @NonNull UserInfo userInfo); + } + + private void onUser(@NonNull String onWhat, @UserIdInt int userHandle, + @NonNull ServiceVisitor visitor) { + onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userHandle, visitor); + } + + private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat, + @UserIdInt int userHandle, @NonNull ServiceVisitor visitor) { + t.traceBegin("ssm." + onWhat + "User-" + userHandle); + Slog.i(TAG, "Calling on" + onWhat + "User u" + userHandle); final UserInfo userInfo = getUserInfo(userHandle); final int serviceLen = mServices.size(); for (int i = 0; i < serviceLen; i++) { final SystemService service = mServices.get(i); final String serviceName = service.getClass().getName(); - t.traceBegin("onCleanupUser-" + userHandle + " " + serviceName); + t.traceBegin("ssm.on" + onWhat + "User-" + userHandle + " " + serviceName); long time = SystemClock.elapsedRealtime(); try { - service.onCleanupUser(userInfo); + visitor.visit(service, userInfo); } catch (Exception ex) { - Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle + Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + userHandle + " to service " + serviceName, ex); } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onCleanupUser"); - t.traceEnd(); + warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "on" + onWhat + "User "); + t.traceEnd(); // what on service } - t.traceEnd(); + t.traceEnd(); // main entry } /** Sets the safe mode flag for services to query. */ diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index e66e596d5038..f7e825eecc12 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -1027,7 +1027,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log(str); } mLocalLog.log(str); - if (validatePhoneId(phoneId)) { + // for service state updates, don't notify clients when subId is invalid. This prevents + // us from sending incorrect notifications like b/133140128 + // In the future, we can remove this logic for every notification here and add a + // callback so listeners know when their PhoneStateListener's subId becomes invalid, but + // for now we use the simplest fix. + if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) { mServiceState[phoneId] = state; for (Record r : mRecords) { @@ -1059,7 +1064,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } } else { - log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId); + log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId + + " or subId=" + subId); } handleRemoveListLocked(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 19c818f4dcfd..7ef6032365d2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -282,6 +282,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.EventLog; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Pair; import android.util.PrintWriterPrinter; @@ -378,7 +379,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -437,6 +438,10 @@ public class ActivityManagerService extends IActivityManager.Stub // need not be the case. public static final String ACTION_TRIGGER_IDLE = "com.android.server.ACTION_TRIGGER_IDLE"; + private static final String INTENT_BUGREPORT_REQUESTED = + "com.android.internal.intent.action.BUGREPORT_REQUESTED"; + private static final String SHELL_APP_PACKAGE = "com.android.shell"; + /** Control over CPU and battery monitoring */ // write battery stats every 30 minutes. static final long BATTERY_STATS_TIME = 30 * 60 * 1000; @@ -555,6 +560,10 @@ public class ActivityManagerService extends IActivityManager.Stub OomAdjuster mOomAdjuster; final LowMemDetector mLowMemDetector; + static final String EXTRA_TITLE = "android.intent.extra.TITLE"; + static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION"; + static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE"; + /** All system services */ SystemServiceManager mSystemServiceManager; @@ -1480,8 +1489,9 @@ public class ActivityManagerService extends IActivityManager.Stub public ActivityTaskManagerService mActivityTaskManager; @VisibleForTesting public ActivityTaskManagerInternal mAtmInternal; + UriGrantsManagerInternal mUgmInternal; @VisibleForTesting - public UriGrantsManagerInternal mUgmInternal; + public final ActivityManagerInternal mInternal; final ActivityThread mSystemThread; private final class AppDeathRecipient implements IBinder.DeathRecipient { @@ -2413,6 +2423,8 @@ public class ActivityManagerService extends IActivityManager.Stub mProcStartHandler = null; mHiddenApiBlacklist = null; mFactoryTest = FACTORY_TEST_OFF; + mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); + mInternal = new LocalService(); } // Note: This method is invoked on the main thread but may need to attach various @@ -2566,6 +2578,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, "Setting background thread cpuset failed"); } + mInternal = new LocalService(); } public void setSystemServiceManager(SystemServiceManager mgr) { @@ -2583,7 +2596,7 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService.publish(); mAppOpsService.publish(mContext); Slog.d("AppOps", "AppOpsService published"); - LocalServices.addService(ActivityManagerInternal.class, new LocalService()); + LocalServices.addService(ActivityManagerInternal.class, mInternal); mActivityTaskManager.onActivityManagerInternalAdded(); mUgmInternal.onActivityManagerInternalAdded(); mPendingIntentController.onActivityManagerInternalAdded(); @@ -8197,6 +8210,53 @@ public class ActivityManagerService extends IActivityManager.Stub @Deprecated @Override public void requestBugReport(int bugreportType) { + requestBugReportWithDescription(null, null, bugreportType); + } + + /** + * @deprecated This method is only used by a few internal components and it will soon be + * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps). + * No new code should be calling it. + */ + @Deprecated + public void requestBugReportWithDescription(@Nullable String shareTitle, + @Nullable String shareDescription, int bugreportType) { + if (!TextUtils.isEmpty(shareTitle)) { + if (shareTitle.length() > MAX_BUGREPORT_TITLE_SIZE) { + String errorStr = "shareTitle should be less than " + + MAX_BUGREPORT_TITLE_SIZE + " characters"; + throw new IllegalArgumentException(errorStr); + } + if (!TextUtils.isEmpty(shareDescription)) { + int length = shareDescription.getBytes(StandardCharsets.UTF_8).length; + if (length > SystemProperties.PROP_VALUE_MAX) { + String errorStr = "shareTitle should be less than " + + SystemProperties.PROP_VALUE_MAX + " bytes"; + throw new IllegalArgumentException(errorStr); + } else { + SystemProperties.set("dumpstate.options.description", shareDescription); + } + } + SystemProperties.set("dumpstate.options.title", shareTitle); + Slog.d(TAG, "Bugreport notification title " + shareTitle + + " description " + shareDescription); + } + final boolean useApi = FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.USE_BUGREPORT_API); + + if (useApi) { + // Create intent to trigger Bugreport API via Shell + Intent triggerShellBugreport = new Intent(); + triggerShellBugreport.setAction(INTENT_BUGREPORT_REQUESTED); + triggerShellBugreport.setPackage(SHELL_APP_PACKAGE); + triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType); + if (shareTitle != null) { + triggerShellBugreport.putExtra(EXTRA_TITLE, shareTitle); + } + if (shareDescription != null) { + triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription); + } + } String extraOptions = null; switch (bugreportType) { case ActivityManager.BUGREPORT_OPTION_FULL: @@ -8238,45 +8298,6 @@ public class ActivityManagerService extends IActivityManager.Stub * No new code should be calling it. */ @Deprecated - private void requestBugReportWithDescription(String shareTitle, String shareDescription, - int bugreportType) { - if (!TextUtils.isEmpty(shareTitle)) { - if (shareTitle.length() > MAX_BUGREPORT_TITLE_SIZE) { - String errorStr = "shareTitle should be less than " + - MAX_BUGREPORT_TITLE_SIZE + " characters"; - throw new IllegalArgumentException(errorStr); - } else { - if (!TextUtils.isEmpty(shareDescription)) { - int length; - try { - length = shareDescription.getBytes("UTF-8").length; - } catch (UnsupportedEncodingException e) { - String errorStr = "shareDescription: UnsupportedEncodingException"; - throw new IllegalArgumentException(errorStr); - } - if (length > SystemProperties.PROP_VALUE_MAX) { - String errorStr = "shareTitle should be less than " + - SystemProperties.PROP_VALUE_MAX + " bytes"; - throw new IllegalArgumentException(errorStr); - } else { - SystemProperties.set("dumpstate.options.description", shareDescription); - } - } - SystemProperties.set("dumpstate.options.title", shareTitle); - } - } - - Slog.d(TAG, "Bugreport notification title " + shareTitle - + " description " + shareDescription); - requestBugReport(bugreportType); - } - - /** - * @deprecated This method is only used by a few internal components and it will soon be - * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps). - * No new code should be calling it. - */ - @Deprecated @Override public void requestTelephonyBugReport(String shareTitle, String shareDescription) { requestBugReportWithDescription(shareTitle, shareDescription, @@ -12511,6 +12532,13 @@ public class ActivityManagerService extends IActivityManager.Stub if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) { opts.dumpDetails = true; } + final int numProcs = procs.size(); + final boolean collectNative = !opts.isCheckinRequest && numProcs > 1 && !opts.packages; + if (collectNative) { + // If we are showing aggregations, also look for native processes to + // include so that our aggregations are more accurate. + updateCpuStatsNow(); + } dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact); @@ -12549,7 +12577,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean hasSwapPss = false; Debug.MemoryInfo mi = null; - for (int i = procs.size() - 1 ; i >= 0 ; i--) { + for (int i = numProcs - 1; i >= 0; i--) { final ProcessRecord r = procs.get(i); final IApplicationThread thread; final int pid; @@ -12703,10 +12731,7 @@ public class ActivityManagerService extends IActivityManager.Stub long nativeProcTotalPss = 0; - if (!opts.isCheckinRequest && procs.size() > 1 && !opts.packages) { - // If we are showing aggregations, also look for native processes to - // include so that our aggregations are more accurate. - updateCpuStatsNow(); + if (collectNative) { mi = null; synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); @@ -13074,6 +13099,13 @@ public class ActivityManagerService extends IActivityManager.Stub if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) { opts.dumpDetails = true; } + final int numProcs = procs.size(); + final boolean collectNative = numProcs > 1 && !opts.packages; + if (collectNative) { + // If we are showing aggregations, also look for native processes to + // include so that our aggregations are more accurate. + updateCpuStatsNow(); + } ProtoOutputStream proto = new ProtoOutputStream(fd); @@ -13115,7 +13147,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean hasSwapPss = false; Debug.MemoryInfo mi = null; - for (int i = procs.size() - 1 ; i >= 0 ; i--) { + for (int i = numProcs - 1; i >= 0; i--) { final ProcessRecord r = procs.get(i); final IApplicationThread thread; final int pid; @@ -13262,10 +13294,7 @@ public class ActivityManagerService extends IActivityManager.Stub long nativeProcTotalPss = 0; - if (procs.size() > 1 && !opts.packages) { - // If we are showing aggregations, also look for native processes to - // include so that our aggregations are more accurate. - updateCpuStatsNow(); + if (collectNative) { mi = null; synchronized (mProcessCpuTracker) { final int N = mProcessCpuTracker.countStats(); diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java index 4a9ccdee0522..766e5c4d638f 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java @@ -209,8 +209,7 @@ public abstract class AuthenticationClient extends ClientMonitor { // will show briefly and be replaced by "device locked out" message. if (listener != null) { if (isBiometricPrompt()) { - listener.onAuthenticationFailedInternal(getCookie(), - getRequireConfirmation()); + listener.onAuthenticationFailedInternal(); } else { listener.onAuthenticationFailed(getHalDeviceId()); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index af2f24f959b4..24e6a75e6844 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -25,11 +25,9 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import android.app.ActivityManager; -import android.app.ActivityTaskManager; import android.app.AppOpsManager; -import android.app.IActivityTaskManager; +import android.app.IActivityManager; import android.app.KeyguardManager; -import android.app.TaskStackListener; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.Context; @@ -69,6 +67,7 @@ import android.util.Slog; import android.util.StatsLog; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IStatusBarService; import com.android.server.SystemService; @@ -88,9 +87,8 @@ public class BiometricService extends SystemService { private static final String TAG = "BiometricService"; private static final boolean DEBUG = true; - private static final int MSG_ON_TASK_STACK_CHANGED = 1; private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2; - private static final int MSG_ON_AUTHENTICATION_FAILED = 3; + private static final int MSG_ON_AUTHENTICATION_REJECTED = 3; private static final int MSG_ON_ERROR = 4; private static final int MSG_ON_ACQUIRED = 5; private static final int MSG_ON_DISMISSED = 6; @@ -101,6 +99,7 @@ public class BiometricService extends SystemService { private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11; private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12; private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13; + private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 14; private static final int[] FEATURE_ID = { TYPE_FINGERPRINT, @@ -112,33 +111,41 @@ public class BiometricService extends SystemService { * Authentication either just called and we have not transitioned to the CALLED state, or * authentication terminated (success or error). */ - private static final int STATE_AUTH_IDLE = 0; + static final int STATE_AUTH_IDLE = 0; /** * Authentication was called and we are waiting for the <Biometric>Services to return their * cookies before starting the hardware and showing the BiometricPrompt. */ - private static final int STATE_AUTH_CALLED = 1; + static final int STATE_AUTH_CALLED = 1; /** * Authentication started, BiometricPrompt is showing and the hardware is authenticating. */ - private static final int STATE_AUTH_STARTED = 2; + static final int STATE_AUTH_STARTED = 2; /** * Authentication is paused, waiting for the user to press "try again" button. Only * passive modalities such as Face or Iris should have this state. Note that for passive * modalities, the HAL enters the idle state after onAuthenticated(false) which differs from * fingerprint. */ - private static final int STATE_AUTH_PAUSED = 3; + static final int STATE_AUTH_PAUSED = 3; /** * Authentication is successful, but we're waiting for the user to press "confirm" button. */ - private static final int STATE_AUTH_PENDING_CONFIRM = 5; + static final int STATE_AUTH_PENDING_CONFIRM = 5; /** * Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential */ - private static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6; + static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6; + /** + * Biometric authenticated, waiting for SysUI to finish animation + */ + static final int STATE_AUTHENTICATED_PENDING_SYSUI = 7; + /** + * Biometric error, waiting for SysUI to finish animation + */ + static final int STATE_ERROR_PENDING_SYSUI = 8; - private final class AuthSession implements IBinder.DeathRecipient { + final class AuthSession implements IBinder.DeathRecipient { // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from // <Biometric>Services before we can start authenticating. Pairs that have been returned // are moved to mModalitiesMatched. @@ -165,10 +172,13 @@ public class BiometricService extends SystemService { final boolean mRequireConfirmation; // The current state, which can be either idle, called, or started - private int mState = STATE_AUTH_IDLE; + int mState = STATE_AUTH_IDLE; // For explicit confirmation, do not send to keystore until the user has confirmed // the authentication. byte[] mTokenEscrow; + // Waiting for SystemUI to complete animation + int mErrorEscrow; + String mErrorStringEscrow; // Timestamp when authentication started private long mStartTimeMs; @@ -244,42 +254,37 @@ public class BiometricService extends SystemService { } } - private final class BiometricTaskStackListener extends TaskStackListener { - @Override - public void onTaskStackChanged() { - mHandler.sendEmptyMessage(MSG_ON_TASK_STACK_CHANGED); - } - } - + private final Injector mInjector; + @VisibleForTesting + final IBiometricService.Stub mImpl; private final AppOpsManager mAppOps; private final boolean mHasFeatureFingerprint; private final boolean mHasFeatureIris; private final boolean mHasFeatureFace; - private final SettingObserver mSettingObserver; + @VisibleForTesting + SettingObserver mSettingObserver; private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; - private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener(); private final Random mRandom = new Random(); - private IFingerprintService mFingerprintService; - private IFaceService mFaceService; - private IActivityTaskManager mActivityTaskManager; - private IStatusBarService mStatusBarService; + @VisibleForTesting + IFingerprintService mFingerprintService; + @VisibleForTesting + IFaceService mFaceService; + @VisibleForTesting + IStatusBarService mStatusBarService; + @VisibleForTesting + KeyStore mKeyStore; // Get and cache the available authenticator (manager) classes. Used since aidl doesn't support // polymorphism :/ final ArrayList<Authenticator> mAuthenticators = new ArrayList<>(); - // Cache the current service that's being used. This is the service which - // cancelAuthentication() must be forwarded to. This is just a cache, and the actual - // check (is caller the current client) is done in the <Biometric>Service. - // Since Settings/System (not application) is responsible for changing preference, this - // should be safe. - private int mCurrentModality; - // The current authentication session, null if idle/done. We need to track both the current // and pending sessions since errors may be sent to either. - private AuthSession mCurrentAuthSession; - private AuthSession mPendingAuthSession; + @VisibleForTesting + AuthSession mCurrentAuthSession; + @VisibleForTesting + AuthSession mPendingAuthSession; // TODO(b/123378871): Remove when moved. // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the @@ -289,15 +294,11 @@ public class BiometricService extends SystemService { // to this receiver. private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; - private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @VisibleForTesting + final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_ON_TASK_STACK_CHANGED: { - handleTaskStackChanged(); - break; - } - case MSG_ON_AUTHENTICATION_SUCCEEDED: { SomeArgs args = (SomeArgs) msg.obj; handleAuthenticationSucceeded( @@ -307,8 +308,8 @@ public class BiometricService extends SystemService { break; } - case MSG_ON_AUTHENTICATION_FAILED: { - handleAuthenticationFailed((String) msg.obj /* failureReason */); + case MSG_ON_AUTHENTICATION_REJECTED: { + handleAuthenticationRejected((String) msg.obj /* failureReason */); break; } @@ -397,6 +398,11 @@ public class BiometricService extends SystemService { break; } + case MSG_ON_AUTHENTICATION_TIMED_OUT: { + handleAuthenticationTimedOut((String) msg.obj /* errorMessage */); + break; + } + default: Slog.e(TAG, "Unknown message: " + msg); break; @@ -422,7 +428,8 @@ public class BiometricService extends SystemService { } } - private final class SettingObserver extends ContentObserver { + @VisibleForTesting + public static class SettingObserver extends ContentObserver { private static final boolean DEFAULT_KEYGUARD_ENABLED = true; private static final boolean DEFAULT_APP_ENABLED = true; @@ -436,6 +443,7 @@ public class BiometricService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION); private final ContentResolver mContentResolver; + private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks; private Map<Integer, Boolean> mFaceEnabledOnKeyguard = new HashMap<>(); private Map<Integer, Boolean> mFaceEnabledForApps = new HashMap<>(); @@ -446,13 +454,15 @@ public class BiometricService extends SystemService { * * @param handler The handler to run {@link #onChange} on, or null if none. */ - SettingObserver(Handler handler) { + public SettingObserver(Context context, Handler handler, + List<BiometricService.EnabledOnKeyguardCallback> callbacks) { super(handler); - mContentResolver = getContext().getContentResolver(); + mContentResolver = context.getContentResolver(); + mCallbacks = callbacks; updateContentObserver(); } - void updateContentObserver() { + public void updateContentObserver() { mContentResolver.unregisterContentObserver(this); mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED, false /* notifyForDescendents */, @@ -495,7 +505,7 @@ public class BiometricService extends SystemService { } } - boolean getFaceEnabledOnKeyguard() { + public boolean getFaceEnabledOnKeyguard() { final int user = ActivityManager.getCurrentUser(); if (!mFaceEnabledOnKeyguard.containsKey(user)) { onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, user); @@ -503,22 +513,23 @@ public class BiometricService extends SystemService { return mFaceEnabledOnKeyguard.get(user); } - boolean getFaceEnabledForApps(int userId) { + public boolean getFaceEnabledForApps(int userId) { + Slog.e(TAG, "getFaceEnabledForApps: " + userId, new Exception()); if (!mFaceEnabledForApps.containsKey(userId)) { onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId); } return mFaceEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED); } - boolean getFaceAlwaysRequireConfirmation(int userId) { + public boolean getFaceAlwaysRequireConfirmation(int userId) { if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) { onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, userId); } return mFaceAlwaysRequireConfirmation.get(userId); } - void notifyEnabledOnKeyguardCallbacks(int userId) { - List<EnabledOnKeyguardCallback> callbacks = mEnabledOnKeyguardCallbacks; + public void notifyEnabledOnKeyguardCallbacks(int userId) { + List<EnabledOnKeyguardCallback> callbacks = mCallbacks; for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).notify(BiometricSourceType.FACE, mFaceEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED), @@ -527,7 +538,7 @@ public class BiometricService extends SystemService { } } - private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { + final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { private final IBiometricEnabledOnKeyguardCallback mCallback; @@ -559,7 +570,8 @@ public class BiometricService extends SystemService { } // Wrap the client's receiver so we can do things with the BiometricDialog first - private final IBiometricServiceReceiverInternal mInternalReceiver = + @VisibleForTesting + final IBiometricServiceReceiverInternal mInternalReceiver = new IBiometricServiceReceiverInternal.Stub() { @Override public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token) @@ -571,10 +583,11 @@ public class BiometricService extends SystemService { } @Override - public void onAuthenticationFailed(int cookie, boolean requireConfirmation) + public void onAuthenticationFailed() throws RemoteException { String failureReason = getContext().getString(R.string.biometric_not_recognized); - mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, failureReason).sendToTarget(); + Slog.v(TAG, "onAuthenticationFailed: " + failureReason); + mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED, failureReason).sendToTarget(); } @Override @@ -583,7 +596,7 @@ public class BiometricService extends SystemService { // soft errors and we should allow the user to try authenticating again instead of // dismissing BiometricPrompt. if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) { - mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, message).sendToTarget(); + mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, message).sendToTarget(); } else { SomeArgs args = SomeArgs.obtain(); args.argi1 = cookie; @@ -873,6 +886,44 @@ public class BiometricService extends SystemService { } } + @VisibleForTesting + static class Injector { + IActivityManager getActivityManagerService() { + return ActivityManager.getService(); + } + + IStatusBarService getStatusBarService() { + return IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + IFingerprintService getFingerprintService() { + return IFingerprintService.Stub.asInterface( + ServiceManager.getService(Context.FINGERPRINT_SERVICE)); + } + + IFaceService getFaceService() { + return IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)); + } + + SettingObserver getSettingObserver(Context context, Handler handler, + List<EnabledOnKeyguardCallback> callbacks) { + return new SettingObserver(context, handler, callbacks); + } + + KeyStore getKeyStore() { + return KeyStore.getInstance(); + } + + boolean isDebugEnabled(Context context, int userId) { + return Utils.isDebugEnabled(context, userId); + } + + void publishBinderService(BiometricService service, IBiometricService.Stub impl) { + service.publishBinderService(Context.BIOMETRIC_SERVICE, impl); + } + } + /** * Initializes the system service. * <p> @@ -883,11 +934,19 @@ public class BiometricService extends SystemService { * @param context The system server context. */ public BiometricService(Context context) { + this(context, new Injector()); + } + + @VisibleForTesting + BiometricService(Context context, Injector injector) { super(context); + mInjector = injector; + mImpl = new BiometricServiceWrapper(); mAppOps = context.getSystemService(AppOpsManager.class); mEnabledOnKeyguardCallbacks = new ArrayList<>(); - mSettingObserver = new SettingObserver(mHandler); + mSettingObserver = mInjector.getSettingObserver(context, mHandler, + mEnabledOnKeyguardCallbacks); final PackageManager pm = context.getPackageManager(); mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); @@ -895,7 +954,7 @@ public class BiometricService extends SystemService { mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); try { - ActivityManager.getService().registerUserSwitchObserver( + injector.getActivityManagerService().registerUserSwitchObserver( new UserSwitchObserver() { @Override public void onUserSwitchComplete(int newUserId) { @@ -913,17 +972,14 @@ public class BiometricService extends SystemService { public void onStart() { // TODO: maybe get these on-demand if (mHasFeatureFingerprint) { - mFingerprintService = IFingerprintService.Stub.asInterface( - ServiceManager.getService(Context.FINGERPRINT_SERVICE)); + mFingerprintService = mInjector.getFingerprintService(); } if (mHasFeatureFace) { - mFaceService = IFaceService.Stub.asInterface( - ServiceManager.getService(Context.FACE_SERVICE)); + mFaceService = mInjector.getFaceService(); } - mActivityTaskManager = ActivityTaskManager.getService(); - mStatusBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mKeyStore = mInjector.getKeyStore(); + mStatusBarService = mInjector.getStatusBarService(); // Cache the authenticators for (int i = 0; i < FEATURE_ID.length; i++) { @@ -934,7 +990,7 @@ public class BiometricService extends SystemService { } } - publishBinderService(Context.BIOMETRIC_SERVICE, new BiometricServiceWrapper()); + mInjector.publishBinderService(this, mImpl); } /** @@ -1068,7 +1124,7 @@ public class BiometricService extends SystemService { } private void logDialogDismissed(int reason) { - if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { + if (reason == BiometricPrompt.DISMISSED_REASON_CONFIRMED) { // Explicit auth, authentication confirmed. // Latency in this case is authenticated -> confirmed. <Biometric>Service // should have the first half (first acquired -> authenticated). @@ -1094,7 +1150,7 @@ public class BiometricService extends SystemService { mCurrentAuthSession.mRequireConfirmation, StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED, latency, - Utils.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId)); + mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId)); } else { final long latency = System.currentTimeMillis() - mCurrentAuthSession.mStartTimeMs; @@ -1122,7 +1178,7 @@ public class BiometricService extends SystemService { BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, error, 0 /* vendorCode */, - Utils.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId), + mInjector.isDebugEnabled(getContext(), mCurrentAuthSession.mUserId), latency); } } @@ -1145,51 +1201,22 @@ public class BiometricService extends SystemService { return modality; } - private void handleTaskStackChanged() { - try { - final List<ActivityManager.RunningTaskInfo> runningTasks = - mActivityTaskManager.getTasks(1); - if (!runningTasks.isEmpty()) { - final String topPackage = runningTasks.get(0).topActivity.getPackageName(); - if (mCurrentAuthSession != null - && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)) { - mStatusBarService.hideBiometricDialog(); - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - mCurrentAuthSession.mClientReceiver.onError( - BiometricConstants.BIOMETRIC_ERROR_CANCELED, - getContext().getString( - com.android.internal.R.string.biometric_error_canceled) - ); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to get running tasks", e); - } - } - private void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] token) { - try { // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded // after user dismissed/canceled dialog). if (mCurrentAuthSession == null) { - Slog.e(TAG, "onAuthenticationSucceeded(): Auth session is null"); + Slog.e(TAG, "handleAuthenticationSucceeded: Auth session is null"); return; } + // Store the auth token and submit it to keystore after the dialog is confirmed / + // animating away. + mCurrentAuthSession.mTokenEscrow = token; if (!requireConfirmation) { - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - KeyStore.getInstance().addAuthToken(token); - mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; + mCurrentAuthSession.mState = STATE_AUTHENTICATED_PENDING_SYSUI; } else { mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis(); - // Store the auth token and submit it to keystore after the confirmation - // button has been pressed. - mCurrentAuthSession.mTokenEscrow = token; mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM; } @@ -1201,12 +1228,13 @@ public class BiometricService extends SystemService { } } - private void handleAuthenticationFailed(String failureReason) { + private void handleAuthenticationRejected(String failureReason) { + Slog.v(TAG, "handleAuthenticationRejected: " + failureReason); try { // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded // after user dismissed/canceled dialog). if (mCurrentAuthSession == null) { - Slog.e(TAG, "onAuthenticationFailed(): Auth session is null"); + Slog.e(TAG, "handleAuthenticationRejected: Auth session is null"); return; } @@ -1225,16 +1253,31 @@ public class BiometricService extends SystemService { } } + private void handleAuthenticationTimedOut(String message) { + Slog.v(TAG, "handleAuthenticationTimedOut: " + message); + try { + // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded + // after user dismissed/canceled dialog). + if (mCurrentAuthSession == null) { + Slog.e(TAG, "handleAuthenticationTimedOut: Auth session is null"); + return; + } + + mStatusBarService.onBiometricAuthenticated(false, message); + mCurrentAuthSession.mState = STATE_AUTH_PAUSED; + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + private void handleOnConfirmDeviceCredentialSuccess() { if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "onCDCASuccess null!"); + Slog.w(TAG, "handleOnConfirmDeviceCredentialSuccess null!"); return; } try { - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); if (mCurrentAuthSession != null) { - mCurrentAuthSession.mState = STATE_AUTH_IDLE; mCurrentAuthSession = null; } } catch (RemoteException e) { @@ -1245,14 +1288,13 @@ public class BiometricService extends SystemService { private void handleOnConfirmDeviceCredentialError(int error, String message) { if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); + Slog.w(TAG, "handleOnConfirmDeviceCredentialError null! Error: " + + error + " " + message); return; } try { - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); mConfirmDeviceCredentialReceiver.onError(error, message); if (mCurrentAuthSession != null) { - mCurrentAuthSession.mState = STATE_AUTH_IDLE; mCurrentAuthSession = null; } } catch (RemoteException e) { @@ -1272,7 +1314,7 @@ public class BiometricService extends SystemService { } private void handleOnError(int cookie, int error, String message) { - Slog.d(TAG, "Error: " + error + " cookie: " + cookie); + Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie); // Errors can either be from the current auth session or the pending auth session. // The pending auth session may receive errors such as ERROR_LOCKOUT before // it becomes the current auth session. Similarly, the current auth session may @@ -1282,6 +1324,9 @@ public class BiometricService extends SystemService { try { if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { + mCurrentAuthSession.mErrorEscrow = error; + mCurrentAuthSession.mErrorStringEscrow = message; + if (mCurrentAuthSession.isFromConfirmDeviceCredential()) { // If we were invoked by ConfirmDeviceCredential, do not delete the current // auth session since we still need to respond to cancel signal while @@ -1293,39 +1338,18 @@ public class BiometricService extends SystemService { mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC; mStatusBarService.hideBiometricDialog(); } else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { - mStatusBarService.onBiometricError(message); + mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI; if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { - mActivityTaskManager.unregisterTaskStackListener( - mTaskStackListener); - mCurrentAuthSession.mClientReceiver.onError(error, message); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; mStatusBarService.hideBiometricDialog(); } else { - // Send errors after the dialog is dismissed. - mHandler.postDelayed(() -> { - try { - if (mCurrentAuthSession != null) { - mActivityTaskManager.unregisterTaskStackListener( - mTaskStackListener); - mCurrentAuthSession.mClientReceiver.onError(error, - message); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; - } - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } - }, BiometricPrompt.HIDE_DIALOG_DELAY); + mStatusBarService.onBiometricError(message); } } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) { // In the "try again" state, we should forward canceled errors to - // the client and and clean up. + // the client and and clean up. The only error we should get here is + // ERROR_CANCELED due to another client kicking us out. mCurrentAuthSession.mClientReceiver.onError(error, message); - mStatusBarService.onBiometricError(message); - mActivityTaskManager.unregisterTaskStackListener( - mTaskStackListener); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mStatusBarService.hideBiometricDialog(); mCurrentAuthSession = null; } else { Slog.e(TAG, "Impossible session error state: " @@ -1335,7 +1359,6 @@ public class BiometricService extends SystemService { && mPendingAuthSession.containsCookie(cookie)) { if (mPendingAuthSession.mState == STATE_AUTH_CALLED) { mPendingAuthSession.mClientReceiver.onError(error, message); - mPendingAuthSession.mState = STATE_AUTH_IDLE; mPendingAuthSession = null; } else { Slog.e(TAG, "Impossible pending session error state: " @@ -1370,42 +1393,50 @@ public class BiometricService extends SystemService { private void handleOnDismissed(int reason) { if (mCurrentAuthSession == null) { - Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null"); + Slog.e(TAG, "onDismissed: " + reason + ", auth session null"); return; } logDialogDismissed(reason); try { - if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Positive button is used by passive modalities as a "confirm" button, - // do not send to client - mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason); - // Cancel authentication. Skip the token/package check since we are cancelling - // from system server. The interface is permission protected so this is fine. - cancelInternal(null /* token */, null /* package */, false /* fromClient */); - } - if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) { - mCurrentAuthSession.mClientReceiver.onError( - BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, - getContext().getString( - com.android.internal.R.string.biometric_error_user_canceled)); - } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) { - // Have the service send the token to KeyStore, and send onAuthenticated - // to the application - KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow); - mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); - } + switch (reason) { + case BiometricPrompt.DISMISSED_REASON_CONFIRMED: + case BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED: + mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow); + mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); + break; - // Do not clean up yet if we are from ConfirmDeviceCredential. We should be in the - // STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC. The session should only be removed when - // ConfirmDeviceCredential is confirmed or canceled. - // TODO(b/123378871): Remove when moved - if (!mCurrentAuthSession.isFromConfirmDeviceCredential()) { - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; + case BiometricPrompt.DISMISSED_REASON_NEGATIVE: + mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason); + // Cancel authentication. Skip the token/package check since we are cancelling + // from system server. The interface is permission protected so this is fine. + cancelInternal(null /* token */, null /* package */, false /* fromClient */); + break; + + case BiometricPrompt.DISMISSED_REASON_USER_CANCEL: + mCurrentAuthSession.mClientReceiver.onError( + BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED, + getContext().getString(R.string.biometric_error_user_canceled)); + // Cancel authentication. Skip the token/package check since we are cancelling + // from system server. The interface is permission protected so this is fine. + cancelInternal(null /* token */, null /* package */, false /* fromClient */); + break; + + case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED: + case BiometricPrompt.DISMISSED_REASON_ERROR: + mCurrentAuthSession.mClientReceiver.onError(mCurrentAuthSession.mErrorEscrow, + mCurrentAuthSession.mErrorStringEscrow); + break; + + default: + Slog.w(TAG, "Unhandled reason: " + reason); + break; } + + // Dialog is gone, auth session is done. + mCurrentAuthSession = null; + } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -1472,8 +1503,8 @@ public class BiometricService extends SystemService { if (!continuing) { mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, - mInternalReceiver, modality, requireConfirmation, userId); - mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + mInternalReceiver, modality, requireConfirmation, userId, + mCurrentAuthSession.mOpPackageName); } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); @@ -1517,8 +1548,6 @@ public class BiometricService extends SystemService { return; } - mCurrentModality = modality; - // Start preparing for authentication. Authentication starts when // all modalities requested have invoked onReadyForAuthentication. authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, @@ -1610,7 +1639,6 @@ public class BiometricService extends SystemService { com.android.internal.R.string.biometric_error_user_canceled) ); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; mCurrentAuthSession = null; mStatusBarService.hideBiometricDialog(); } catch (RemoteException e) { @@ -1637,25 +1665,31 @@ public class BiometricService extends SystemService { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); - mHandler.post(() -> { - try { - // TODO: For multiple modalities, send a single ERROR_CANCELED only when all - // drivers have canceled authentication. - if ((mCurrentModality & TYPE_FINGERPRINT) != 0) { - mFingerprintService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId, fromClient); - } - if ((mCurrentModality & TYPE_IRIS) != 0) { - Slog.w(TAG, "Iris unsupported"); - } - if ((mCurrentModality & TYPE_FACE) != 0) { - mFaceService.cancelAuthenticationFromService(token, opPackageName, - callingUid, callingPid, callingUserId, fromClient); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to cancel authentication"); + + try { + if (mCurrentAuthSession == null) { + Slog.w(TAG, "Skipping cancelInternal"); + return; + } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState); + return; } - }); - } + // TODO: For multiple modalities, send a single ERROR_CANCELED only when all + // drivers have canceled authentication. + if ((mCurrentAuthSession.mModality & TYPE_FINGERPRINT) != 0) { + mFingerprintService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + if ((mCurrentAuthSession.mModality & TYPE_IRIS) != 0) { + Slog.w(TAG, "Iris unsupported"); + } + if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { + mFaceService.cancelAuthenticationFromService(token, opPackageName, + callingUid, callingPid, callingUserId, fromClient); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to cancel authentication"); + } + } } diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index f3f9754bd32b..2de18c391d4a 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -420,7 +420,7 @@ public abstract class BiometricServiceBase extends SystemService throw new UnsupportedOperationException("Stub!"); } - default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation) + default void onAuthenticationFailedInternal() throws RemoteException { throw new UnsupportedOperationException("Stub!"); } @@ -457,10 +457,10 @@ public abstract class BiometricServiceBase extends SystemService } @Override - public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation) + public void onAuthenticationFailedInternal() throws RemoteException { if (getWrapperReceiver() != null) { - getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation); + getWrapperReceiver().onAuthenticationFailed(); } } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 53890a48a674..a0eafb4fc93f 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -35,6 +35,8 @@ import android.hardware.broadcastradio.V2_0.Result; import android.hardware.broadcastradio.V2_0.VendorKeyValue; import android.hardware.radio.RadioManager; import android.os.DeadObjectException; +import android.os.Handler; +import android.os.Looper; import android.os.RemoteException; import android.util.MutableInt; import android.util.Slog; @@ -45,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -56,6 +59,7 @@ class RadioModule { @NonNull public final RadioManager.ModuleProperties mProperties; private final Object mLock = new Object(); + @NonNull private final Handler mHandler; @GuardedBy("mLock") private ITunerSession mHalTunerSession; @@ -77,22 +81,24 @@ class RadioModule { private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() { @Override public void onTuneFailed(int result, ProgramSelector programSelector) { - fanoutAidlCallback(cb -> cb.onTuneFailed(result, Convert.programSelectorFromHal( - programSelector))); + lockAndFireLater(() -> { + android.hardware.radio.ProgramSelector csel = + Convert.programSelectorFromHal(programSelector); + fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel)); + }); } @Override public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) { - RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo); - synchronized (mLock) { - mCurrentProgramInfo = programInfo; - fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(programInfo)); - } + lockAndFireLater(() -> { + mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo); + fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mCurrentProgramInfo)); + }); } @Override public void onProgramListUpdated(ProgramListChunk programListChunk) { - synchronized (mLock) { + lockAndFireLater(() -> { android.hardware.radio.ProgramList.Chunk chunk = Convert.programListChunkFromHal(programListChunk); mProgramInfoCache.filterAndApplyChunk(chunk); @@ -100,20 +106,23 @@ class RadioModule { for (TunerSession tunerSession : mAidlTunerSessions) { tunerSession.onMergedProgramListUpdateFromHal(chunk); } - } + }); } @Override public void onAntennaStateChange(boolean connected) { - synchronized (mLock) { + lockAndFireLater(() -> { mAntennaConnected = connected; fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected)); - } + }); } @Override public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) { - fanoutAidlCallback(cb -> cb.onParametersUpdated(Convert.vendorInfoFromHal(parameters))); + lockAndFireLater(() -> { + Map<String, String> cparam = Convert.vendorInfoFromHal(parameters); + fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam)); + }); } }; @@ -126,6 +135,7 @@ class RadioModule { @NonNull RadioManager.ModuleProperties properties) { mProperties = Objects.requireNonNull(properties); mService = Objects.requireNonNull(service); + mHandler = new Handler(Looper.getMainLooper()); } public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) { @@ -310,15 +320,22 @@ class RadioModule { } } + // add to mHandler queue, but ensure the runnable holds mLock when it gets executed + private void lockAndFireLater(Runnable r) { + mHandler.post(() -> { + synchronized (mLock) { + r.run(); + } + }); + } + interface AidlCallbackRunnable { void run(android.hardware.radio.ITunerCallback callback) throws RemoteException; } // Invokes runnable with each TunerSession currently open. void fanoutAidlCallback(AidlCallbackRunnable runnable) { - synchronized (mLock) { - fanoutAidlCallbackLocked(runnable); - } + lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable)); } private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) { diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 3e1817bd14ff..36e872a109dd 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -461,7 +461,7 @@ public final class ContentService extends IContentService.Stub { } synchronized (mCache) { - final String providerPackageName = getProviderPackageName(uri); + final String providerPackageName = getProviderPackageName(uri, userHandle); invalidateCacheLocked(userHandle, providerPackageName, uri); } } finally { @@ -1145,9 +1145,9 @@ public final class ContentService extends IContentService.Stub { } } - private @Nullable String getProviderPackageName(Uri uri) { - final ProviderInfo pi = mContext.getPackageManager() - .resolveContentProvider(uri.getAuthority(), 0); + private @Nullable String getProviderPackageName(Uri uri, int userId) { + final ProviderInfo pi = mContext.getPackageManager().resolveContentProviderAsUser( + uri.getAuthority(), 0, userId); return (pi != null) ? pi.packageName : null; } @@ -1200,7 +1200,7 @@ public final class ContentService extends IContentService.Stub { mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(), packageName); - final String providerPackageName = getProviderPackageName(key); + final String providerPackageName = getProviderPackageName(key, userId); final Pair<String, Uri> fullKey = Pair.create(packageName, key); synchronized (mCache) { @@ -1222,7 +1222,7 @@ public final class ContentService extends IContentService.Stub { mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(), packageName); - final String providerPackageName = getProviderPackageName(key); + final String providerPackageName = getProviderPackageName(key, userId); final Pair<String, Uri> fullKey = Pair.create(packageName, key); synchronized (mCache) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index aa548f260450..e90db3855b65 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -621,6 +621,8 @@ public class NotificationManagerService extends SystemService { mConditionProviders.readXml( parser, mAllowedManagedServicePackages, forRestore, userId); migratedManagedServices = true; + } else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) { + mSnoozeHelper.readXml(parser); } if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) { if (forRestore && userId != UserHandle.USER_SYSTEM) { @@ -709,6 +711,7 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.writeXml(out, forBackup, userId); mListeners.writeXml(out, forBackup, userId); mAssistants.writeXml(out, forBackup, userId); + mSnoozeHelper.writeXml(out); mConditionProviders.writeXml(out, forBackup, userId); if (!forBackup || userId == UserHandle.USER_SYSTEM) { writeSecureNotificationsPolicy(out); @@ -1753,6 +1756,7 @@ public class NotificationManagerService extends SystemService { com.android.internal.R.integer.config_notificationWarnRemoteViewSizeBytes); mStripRemoteViewsSizeBytes = getContext().getResources().getInteger( com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes); + } @Override @@ -5284,7 +5288,7 @@ public class NotificationManagerService extends SystemService { updateLightsLocked(); if (mSnoozeCriterionId != null) { mAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId); - mSnoozeHelper.snooze(r); + mSnoozeHelper.snooze(r, mSnoozeCriterionId); } else { mSnoozeHelper.snooze(r, mDuration); } @@ -5387,6 +5391,27 @@ public class NotificationManagerService extends SystemService { @Override public void run() { synchronized (mNotificationLock) { + final Long snoozeAt = + mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + r.getUser().getIdentifier(), + r.sbn.getPackageName(), r.sbn.getKey()); + final long currentTime = System.currentTimeMillis(); + if (snoozeAt.longValue() > currentTime) { + (new SnoozeNotificationRunnable(r.sbn.getKey(), + snoozeAt.longValue() - currentTime, null)).snoozeLocked(r); + return; + } + + final String contextId = + mSnoozeHelper.getSnoozeContextForUnpostedNotification( + r.getUser().getIdentifier(), + r.sbn.getPackageName(), r.sbn.getKey()); + if (contextId != null) { + (new SnoozeNotificationRunnable(r.sbn.getKey(), + 0, contextId)).snoozeLocked(r); + return; + } + mEnqueuedNotifications.add(r); scheduleTimeoutLocked(r); @@ -6937,6 +6962,7 @@ public class NotificationManagerService extends SystemService { if (DBG) { Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); } + mSnoozeHelper.cleanupPersistedContext(key); mSnoozeHelper.repost(key); handleSavePolicyFile(); } diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 91f497cf9607..8125d0d653ad 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -55,6 +55,21 @@ import java.util.Set; * NotificationManagerService helper for handling snoozed notifications. */ public class SnoozeHelper { + public static final String XML_SNOOZED_NOTIFICATION_VERSION = "1"; + + protected static final String XML_TAG_NAME = "snoozed-notifications"; + + private static final String XML_SNOOZED_NOTIFICATION = "notification"; + private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context"; + private static final String XML_SNOOZED_NOTIFICATION_PKG = "pkg"; + private static final String XML_SNOOZED_NOTIFICATION_USER_ID = "user-id"; + private static final String XML_SNOOZED_NOTIFICATION_KEY = "key"; + //the time the snoozed notification should be reposted + private static final String XML_SNOOZED_NOTIFICATION_TIME = "time"; + private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id"; + private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version"; + + private static final String TAG = "SnoozeHelper"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String INDENT = " "; @@ -72,6 +87,17 @@ public class SnoozeHelper { // User id : package name : notification key : record. private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>> mSnoozedNotifications = new ArrayMap<>(); + // User id : package name : notification key : time-milliseconds . + // This member stores persisted snoozed notification trigger times. it persists through reboots + // It should have the notifications that haven't expired or re-posted yet + private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, Long>>> + mPersistedSnoozedNotifications = new ArrayMap<>(); + // User id : package name : notification key : creation ID . + // This member stores persisted snoozed notification trigger context for the assistant + // it persists through reboots. + // It should have the notifications that haven't expired or re-posted yet + private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, String>>> + mPersistedSnoozedNotificationsWithContext = new ArrayMap<>(); // notification key : package. private ArrayMap<String, String> mPackages = new ArrayMap<>(); // key : userId @@ -89,6 +115,34 @@ public class SnoozeHelper { mUserProfiles = userProfiles; } + void cleanupPersistedContext(String key){ + int userId = mUsers.get(key); + String pkg = mPackages.get(key); + synchronized (mPersistedSnoozedNotificationsWithContext) { + removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); + } + } + + //This function has a side effect of removing the time from the list of persisted notifications. + //IT IS NOT IDEMPOTENT! + @NonNull + protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) { + Long time; + synchronized (mPersistedSnoozedNotifications) { + time = removeRecord(pkg, key, userId, mPersistedSnoozedNotifications); + } + if (time == null) { + return 0L; + } + return time; + } + + protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) { + synchronized (mPersistedSnoozedNotificationsWithContext) { + return removeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext); + } + } + protected boolean isSnoozed(int userId, String pkg, String key) { return mSnoozedNotifications.containsKey(userId) && mSnoozedNotifications.get(userId).containsKey(pkg) @@ -169,32 +223,82 @@ public class SnoozeHelper { * Snoozes a notification and schedules an alarm to repost at that time. */ protected void snooze(NotificationRecord record, long duration) { + String pkg = record.sbn.getPackageName(); + String key = record.getKey(); + int userId = record.getUser().getIdentifier(); + snooze(record); - scheduleRepost(record.sbn.getPackageName(), record.getKey(), record.getUserId(), duration); + scheduleRepost(pkg, key, userId, duration); + Long activateAt = System.currentTimeMillis() + duration; + synchronized (mPersistedSnoozedNotifications) { + storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, activateAt); + } } /** * Records a snoozed notification. */ - protected void snooze(NotificationRecord record) { + protected void snooze(NotificationRecord record, String contextId) { + int userId = record.getUser().getIdentifier(); + if (contextId != null) { + synchronized (mPersistedSnoozedNotificationsWithContext) { + storeRecord(record.sbn.getPackageName(), record.getKey(), + userId, mPersistedSnoozedNotificationsWithContext, contextId); + } + } + snooze(record); + } + + private void snooze(NotificationRecord record) { int userId = record.getUser().getIdentifier(); if (DEBUG) { Slog.d(TAG, "Snoozing " + record.getKey()); } - ArrayMap<String, ArrayMap<String, NotificationRecord>> records = - mSnoozedNotifications.get(userId); + storeRecord(record.sbn.getPackageName(), record.getKey(), + userId, mSnoozedNotifications, record); + mPackages.put(record.getKey(), record.sbn.getPackageName()); + mUsers.put(record.getKey(), userId); + } + + private <T> void storeRecord(String pkg, String key, Integer userId, + ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, T object) { + + ArrayMap<String, ArrayMap<String, T>> records = + targets.get(userId); if (records == null) { records = new ArrayMap<>(); } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.sbn.getPackageName()); + ArrayMap<String, T> pkgRecords = records.get(pkg); if (pkgRecords == null) { pkgRecords = new ArrayMap<>(); } - pkgRecords.put(record.getKey(), record); - records.put(record.sbn.getPackageName(), pkgRecords); - mSnoozedNotifications.put(userId, records); - mPackages.put(record.getKey(), record.sbn.getPackageName()); - mUsers.put(record.getKey(), userId); + pkgRecords.put(key, object); + records.put(pkg, pkgRecords); + targets.put(userId, records); + + } + + private <T> T removeRecord(String pkg, String key, Integer userId, + ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets) { + T object = null; + + ArrayMap<String, ArrayMap<String, T>> records = + targets.get(userId); + if (records == null) { + return null; + } + ArrayMap<String, T> pkgRecords = records.get(pkg); + if (pkgRecords == null) { + return null; + } + object = pkgRecords.remove(key); + if (pkgRecords.size() == 0) { + records.remove(pkg); + } + if (records.size() == 0) { + targets.remove(userId); + } + return object; } protected boolean cancel(int userId, String pkg, String tag, int id) { @@ -414,13 +518,121 @@ public class SnoozeHelper { } } - protected void writeXml(XmlSerializer out, boolean forBackup) throws IOException { + protected void writeXml(XmlSerializer out) throws IOException { + final long currentTime = System.currentTimeMillis(); + out.startTag(null, XML_TAG_NAME); + writeXml(out, mPersistedSnoozedNotifications, XML_SNOOZED_NOTIFICATION, + value -> { + if (value < currentTime) { + return; + } + out.attribute(null, XML_SNOOZED_NOTIFICATION_TIME, + value.toString()); + }); + writeXml(out, mPersistedSnoozedNotificationsWithContext, XML_SNOOZED_NOTIFICATION_CONTEXT, + value -> { + out.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, + value); + }); + out.endTag(null, XML_TAG_NAME); + } + + private interface Inserter<T> { + void insert(T t) throws IOException; + } + private <T> void writeXml(XmlSerializer out, + ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, String tag, + Inserter<T> attributeInserter) + throws IOException { + synchronized (targets) { + final int M = targets.size(); + for (int i = 0; i < M; i++) { + final ArrayMap<String, ArrayMap<String, T>> packages = + targets.valueAt(i); + if (packages == null) { + continue; + } + final int N = packages.size(); + for (int j = 0; j < N; j++) { + final ArrayMap<String, T> keyToValue = packages.valueAt(j); + if (keyToValue == null) { + continue; + } + final int O = keyToValue.size(); + for (int k = 0; k < O; k++) { + T value = keyToValue.valueAt(k); + + out.startTag(null, tag); + attributeInserter.insert(value); + + out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, + XML_SNOOZED_NOTIFICATION_VERSION); + out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, keyToValue.keyAt(k)); + out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, packages.keyAt(j)); + out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID, + targets.keyAt(i).toString()); + + out.endTag(null, tag); + + } + } + } + } } - public void readXml(XmlPullParser parser, boolean forRestore) + protected void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + String tag = parser.getName(); + if (type == XmlPullParser.END_TAG + && XML_TAG_NAME.equals(tag)) { + break; + } + if (type == XmlPullParser.START_TAG + && (XML_SNOOZED_NOTIFICATION.equals(tag) + || tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) + && parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL) + .equals(XML_SNOOZED_NOTIFICATION_VERSION)) { + try { + final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY); + final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG); + final int userId = Integer.parseInt( + parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_USER_ID)); + if (tag.equals(XML_SNOOZED_NOTIFICATION)) { + final Long time = Long.parseLong( + parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_TIME)); + if (time > System.currentTimeMillis()) { //only read new stuff + synchronized (mPersistedSnoozedNotifications) { + storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, time); + } + scheduleRepost(pkg, key, userId, time - System.currentTimeMillis()); + } + continue; + } + if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) { + final String creationId = parser.getAttributeValue( + null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID); + synchronized (mPersistedSnoozedNotificationsWithContext) { + storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext, + creationId); + } + continue; + } + + } catch (Exception e) { + //we dont cre if it is a number format exception or a null pointer exception. + //we just want to debug it and continue with our lives + if (DEBUG) { + Slog.d(TAG, + "Exception in reading snooze data from policy xml: " + + e.getMessage()); + } + } + } + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index 91824c3e9f43..20a4c75a1ce6 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -129,11 +129,11 @@ class IdmapDaemon { } } - static void startIdmapService() { + private static void startIdmapService() { SystemProperties.set("ctl.start", IDMAP_DAEMON); } - static void stopIdmapService() { + private static void stopIdmapService() { SystemProperties.set("ctl.stop", IDMAP_DAEMON); } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 8171a8d45ffa..965ddc9f2782 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -262,7 +262,6 @@ public final class OverlayManagerService extends SystemService { initIfNeeded(); onSwitchUser(UserHandle.USER_SYSTEM); - IdmapDaemon.stopIdmapService(); publishBinderService(Context.OVERLAY_SERVICE, mService); publishLocalService(OverlayManagerService.class, this); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 0032e9a8ea51..3aeb2b102afe 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1184,9 +1184,26 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { pw.println("Active install sessions:"); pw.increaseIndent(); + + List<PackageInstallerSession> finalizedSessions = new ArrayList<>(); int N = mSessions.size(); for (int i = 0; i < N; i++) { final PackageInstallerSession session = mSessions.valueAt(i); + if (session.isStagedAndInTerminalState()) { + finalizedSessions.add(session); + continue; + } + session.dump(pw); + pw.println(); + } + pw.println(); + pw.decreaseIndent(); + + pw.println("Finalized install sessions:"); + pw.increaseIndent(); + N = finalizedSessions.size(); + for (int i = 0; i < N; i++) { + final PackageInstallerSession session = finalizedSessions.get(i); session.dump(pw); pw.println(); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4eddb9301a69..b72029046067 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2337,6 +2337,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("mInstallerPackageName", mInstallerPackageName); pw.printPair("mInstallerUid", mInstallerUid); pw.printPair("createdMillis", createdMillis); + pw.printPair("updatedMillis", updatedMillis); pw.printPair("stageDir", stageDir); pw.printPair("stageCid", stageCid); pw.println(); @@ -2356,6 +2357,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("mFinalMessage", mFinalMessage); pw.printPair("params.isMultiPackage", params.isMultiPackage); pw.printPair("params.isStaged", params.isStaged); + pw.printPair("mParentSessionId", mParentSessionId); + pw.printPair("mChildSessionIds", mChildSessionIds); + pw.printPair("mStagedSessionApplied", mStagedSessionApplied); + pw.printPair("mStagedSessionFailed", mStagedSessionFailed); + pw.printPair("mStagedSessionReady", mStagedSessionReady); + pw.printPair("mStagedSessionErrorCode", mStagedSessionErrorCode); + pw.printPair("mStagedSessionErrorMessage", mStagedSessionErrorMessage); pw.println(); pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 02f98b4e7376..bfd280c14f6d 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -271,33 +271,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { }, filter, null, getHandler()); } - /** - * This method posts a blocking call to the handler thread, so it should not be called from - * that same thread. - * @throws {@link IllegalStateException} if called from {@link #mHandlerThread} - */ @Override public ParceledListSlice getAvailableRollbacks() { enforceManageRollbacks("getAvailableRollbacks"); - if (Thread.currentThread().equals(mHandlerThread)) { - Slog.wtf(TAG, "Calling getAvailableRollbacks from mHandlerThread " - + "causes a deadlock"); - throw new IllegalStateException("Cannot call RollbackManager#getAvailableRollbacks " - + "from the handler thread!"); - } - - // Wait for the handler thread to get the list of available rollbacks - // to get the most up-to-date results. This is intended to reduce test - // flakiness when checking available rollbacks immediately after - // installing a package with rollback enabled. - CountDownLatch latch = new CountDownLatch(1); - getHandler().post(() -> latch.countDown()); - try { - latch.await(); - } catch (InterruptedException ie) { - throw new IllegalStateException("RollbackManagerHandlerThread interrupted"); - } - synchronized (mLock) { List<RollbackInfo> rollbacks = new ArrayList<>(); for (int i = 0; i < mRollbacks.size(); ++i) { @@ -306,6 +282,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { rollbacks.add(rollback.info); } } + + // Also return new rollbacks for which the PackageRollbackInfo is complete. + for (NewRollback newRollback : mNewRollbacks) { + if (newRollback.rollback.info.getPackages().size() + == newRollback.packageSessionIds.length + && !newRollback.isCancelled) { + rollbacks.add(newRollback.rollback.info); + } + } return new ParceledListSlice<>(rollbacks); } } @@ -562,6 +547,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } } } + for (NewRollback newRollback : mNewRollbacks) { + for (PackageRollbackInfo info : newRollback.rollback.info.getPackages()) { + if (info.getPackageName().equals(packageName)) { + newRollback.isCancelled = true; + break; + } + } + } } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index d67048fe548d..8897eca85d7a 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -610,11 +610,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, - int type, boolean requireConfirmation, int userId) { + int type, boolean requireConfirmation, int userId, String opPackageName) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.showBiometricDialog(bundle, receiver, type, requireConfirmation, userId); + mBar.showBiometricDialog(bundle, receiver, type, requireConfirmation, userId, + opPackageName); } catch (RemoteException ex) { } } diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index f1cd721cfd8c..5a0dfd02b7b0 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -41,6 +41,7 @@ import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY; import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE; import static com.android.server.am.ActivityDisplayProto.STACKS; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; +import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK; @@ -173,18 +174,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> mService = root.mService; mDisplayId = display.getDisplayId(); mDisplay = display; - mDisplayContent = createDisplayContent(); + mDisplayContent = mService.mWindowManager.mRoot.createDisplayContent(mDisplay, this); mDisplayContent.reconfigureDisplayLocked(); - updateBounds(); - } - - protected DisplayContent createDisplayContent() { - return mService.mWindowManager.mRoot.createDisplayContent(mDisplay, this); - } - - private void updateBounds() { - mDisplay.getRealSize(mTmpDisplaySize); - setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); + onRequestedOverrideConfigurationChanged( + mDisplayContent.getRequestedOverrideConfiguration()); } void onDisplayChanged() { @@ -200,7 +193,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } } - updateBounds(); + mDisplay.getRealSize(mTmpDisplaySize); + setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); if (mDisplayContent != null) { mDisplayContent.updateDisplayInfo(); mService.mWindowManager.requestTraversal(); @@ -1541,6 +1535,17 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return mSingleTaskInstance; } + @VisibleForTesting + void removeAllTasks() { + for (int i = getChildCount() - 1; i >= 0; --i) { + final ActivityStack stack = getChildAt(i); + final ArrayList<TaskRecord> tasks = stack.getAllTasks(); + for (int j = tasks.size() - 1; j >= 0; --j) { + stack.removeTask(tasks.get(j), "removeAllTasks", REMOVE_TASK_MODE_DESTROYING); + } + } + } + public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size() + (mSingleTaskInstance ? " mSingleTaskInstance" : "")); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 31e8bbdab71c..2269537c7cae 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -121,6 +121,7 @@ import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG; import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY; +import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS; @@ -1372,16 +1373,17 @@ final class ActivityRecord extends ConfigurationContainer { return stack != null ? stack.getDisplay() : null; } - boolean changeWindowTranslucency(boolean toOpaque) { - if (fullscreen == toOpaque) { - return false; + boolean setOccludesParent(boolean occludesParent) { + final boolean changed = mAppWindowToken.setOccludesParent(occludesParent); + if (changed) { + if (!occludesParent) { + getActivityStack().convertActivityToTranslucent(this); + } + // Keep track of the number of fullscreen activities in this task. + task.numFullscreen += occludesParent ? +1 : -1; + mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); } - - // Keep track of the number of fullscreen activities in this task. - task.numFullscreen += toOpaque ? +1 : -1; - - fullscreen = toOpaque; - return true; + return changed; } void takeFromHistory() { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 12eab5096a13..daf32862e287 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2492,7 +2492,7 @@ class ActivityStack extends ConfigurationContainer { mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG); if (waitingActivity != null) { - mWindowManager.setWindowOpaque(waitingActivity.appToken, false); + mWindowManager.setWindowOpaqueLocked(waitingActivity.appToken, false); if (waitingActivity.attachedToProcess()) { try { waitingActivity.app.getThread().scheduleTranslucentConversionComplete( @@ -2813,7 +2813,7 @@ class ActivityStack extends ConfigurationContainer { // Launching this app's activity, make sure the app is no longer // considered stopped. try { - AppGlobals.getPackageManager().setPackageStoppedState( + mService.getPackageManager().setPackageStoppedState( next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */ } catch (RemoteException e1) { } catch (IllegalArgumentException e) { diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 1c56a107ab9e..22f72a499aff 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -250,7 +250,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { RecentTasks mRecentTasks; /** Helper class to abstract out logic for fetching the set of currently running tasks */ - RunningTasks mRunningTasks; + private RunningTasks mRunningTasks; final ActivityStackSupervisorHandler mHandler; final Looper mLooper; @@ -444,7 +444,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } mInitialized = true; - mRunningTasks = createRunningTasks(); + setRunningTasks(new RunningTasks()); mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext, mHandler.getLooper()); @@ -485,13 +485,20 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } void setRecentTasks(RecentTasks recentTasks) { + if (mRecentTasks != null) { + mRecentTasks.unregisterCallback(this); + } mRecentTasks = recentTasks; mRecentTasks.registerCallback(this); } @VisibleForTesting - RunningTasks createRunningTasks() { - return new RunningTasks(); + void setRunningTasks(RunningTasks runningTasks) { + mRunningTasks = runningTasks; + } + + RunningTasks getRunningTasks() { + return mRunningTasks; } /** @@ -2735,7 +2742,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mWindowManager.deferSurfaceLayout(); try { if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - mWindowManager.setDockedStackCreateState( + mWindowManager.setDockedStackCreateStateLocked( activityOptions.getSplitScreenCreateMode(), null /* initialBounds */); // Defer updating the stack in which recents is until the app transition is done, to diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index a3ab27e4f06b..a7b6e0ff0630 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -43,7 +43,6 @@ import static android.content.pm.ApplicationInfo.FLAG_FACTORY_TEST; import static android.content.pm.ConfigurationInfo.GL_ES_VERSION_UNDEFINED; import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; -import static android.content.pm.PackageManager.FEATURE_PC; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION; @@ -211,7 +210,7 @@ import android.service.voice.VoiceInteractionManagerInternal; import android.sysprop.DisplayProperties; import android.telecom.TelecomManager; import android.text.TextUtils; -import android.text.format.Time; +import android.text.format.TimeMigrationUtils; import android.util.ArrayMap; import android.util.EventLog; import android.util.Log; @@ -361,7 +360,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /* Global service lock used by the package the owns this service. */ final WindowManagerGlobalLock mGlobalLock = new WindowManagerGlobalLock(); /** - * It is the same instance as {@link mGlobalLock}, just declared as a type that the + * It is the same instance as {@link #mGlobalLock}, just declared as a type that the * locked-region-code-injection does't recognize it. It is used to skip wrapping priority * booster for places that are already in the scope of another booster (e.g. computing oom-adj). * @@ -730,7 +729,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0; final boolean forceResizable = Settings.Global.getInt( resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0; - final boolean isPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); // Transfer any global setting for forcing RTL layout, into a System Property DisplayProperties.debug_force_rtl(forceRtl); @@ -761,10 +759,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mSupportsPictureInPicture = false; mSupportsMultiDisplay = false; } - mWindowManager.setForceResizableTasks(mForceResizableActivities); - mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture); - mWindowManager.setSupportsFreeformWindowManagement(mSupportsFreeformWindowManagement); - mWindowManager.setIsPc(isPc); mWindowManager.mRoot.onSettingsRetrieved(); // This happens before any activities are started, so we can change global configuration // in-place. @@ -821,8 +815,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { new TaskChangeNotificationController(mGlobalLock, mStackSupervisor, mH); mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mH); mActivityStartController = new ActivityStartController(this); - mRecentTasks = createRecentTasks(); - mStackSupervisor.setRecentTasks(mRecentTasks); + setRecentTasks(new RecentTasks(this, mStackSupervisor)); mVrController = new VrController(mGlobalLock); mKeyguardController = mStackSupervisor.getKeyguardController(); } @@ -890,8 +883,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mode == AppOpsManager.MODE_ALLOWED; } - protected RecentTasks createRecentTasks() { - return new RecentTasks(this, mStackSupervisor); + @VisibleForTesting + protected void setRecentTasks(RecentTasks recentTasks) { + mRecentTasks = recentTasks; + mStackSupervisor.setRecentTasks(recentTasks); } RecentTasks getRecentTasks() { @@ -1954,12 +1949,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (r == null) { return false; } - final boolean translucentChanged = r.changeWindowTranslucency(true); - if (translucentChanged) { - mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - } - mWindowManager.setAppFullscreen(token, true); - return translucentChanged; + return r.setOccludesParent(true); } } finally { Binder.restoreCallingIdentity(origId); @@ -1982,13 +1972,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { ActivityRecord under = task.mActivities.get(index - 1); under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null; } - final boolean translucentChanged = r.changeWindowTranslucency(false); - if (translucentChanged) { - r.getActivityStack().convertActivityToTranslucent(r); - } - mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - mWindowManager.setAppFullscreen(token, false); - return translucentChanged; + return r.setOccludesParent(false); } } finally { Binder.restoreCallingIdentity(origId); @@ -2581,7 +2565,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { + taskId + " to stack " + stackId); } if (stack.inSplitScreenPrimaryWindowingMode()) { - mWindowManager.setDockedStackCreateState( + mWindowManager.setDockedStackCreateStateLocked( SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */); } task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, @@ -2700,7 +2684,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { + " non-standard task " + taskId + " to split-screen windowing mode"); } - mWindowManager.setDockedStackCreateState(createMode, initialBounds); + mWindowManager.setDockedStackCreateStateLocked(createMode, initialBounds); final int windowingMode = task.getWindowingMode(); final ActivityStack stack = task.getStack(); if (toTop) { @@ -2802,7 +2786,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { try { synchronized (mGlobalLock) { // Cancel the recents animation synchronously (do not hold the WM lock) - mWindowManager.cancelRecentsAnimationSynchronously(restoreHomeStackPosition + mWindowManager.cancelRecentsAnimation(restoreHomeStackPosition ? REORDER_MOVE_TO_ORIGINAL_POSITION : REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation/uid=" + callingUid); } @@ -5878,9 +5862,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { tracesFile = File.createTempFile("app_slow", null, tracesDir); StringBuilder sb = new StringBuilder(); - Time tobj = new Time(); - tobj.set(System.currentTimeMillis()); - sb.append(tobj.format("%Y-%m-%d %H:%M:%S")); + String timeString = + TimeMigrationUtils.formatMillisWithFixedFormat(System.currentTimeMillis()); + sb.append(timeString); sb.append(": "); TimeUtils.formatDuration(SystemClock.uptimeMillis()-startTime, sb); sb.append(" since "); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 7fde6dee63e3..7e0d9a05c524 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -92,6 +92,7 @@ import android.content.ComponentName; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.GraphicBuffer; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; @@ -153,8 +154,11 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree final ComponentName mActivityComponent; final boolean mVoiceInteraction; - /** @see WindowContainer#fillsParent() */ - private boolean mFillsParent; + /** + * The activity is opaque and fills the entire space of this task. + * @see WindowContainer#fillsParent() + */ + private boolean mOccludesParent; boolean mShowForAllUsers; int mTargetSdk; @@ -373,7 +377,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree appToken = token; mActivityComponent = activityComponent; mVoiceInteraction = voiceInteraction; - mFillsParent = fillsParent; + mOccludesParent = fillsParent; mInputApplicationHandle = new InputApplicationHandle(appToken.asBinder()); } @@ -2354,11 +2358,29 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override boolean fillsParent() { - return mFillsParent; + return occludesParent(); + } + + /** Returns true if this activity is opaque and fills the entire space of this task. */ + boolean occludesParent() { + return mOccludesParent; + } + + boolean setOccludesParent(boolean occludesParent) { + final boolean changed = occludesParent != mOccludesParent; + mOccludesParent = occludesParent; + setMainWindowOpaque(occludesParent); + mWmService.mWindowPlacerLocked.requestTraversal(); + return changed; } - void setFillsParent(boolean fillsParent) { - mFillsParent = fillsParent; + void setMainWindowOpaque(boolean isOpaque) { + final WindowState win = findMainWindow(); + if (win == null) { + return; + } + isOpaque = isOpaque & !PixelFormat.formatHasAlpha(win.getAttrs().format); + win.mWinAnimator.setOpaqueLocked(isOpaque); } boolean containsDismissKeyguardWindow() { @@ -3035,7 +3057,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } pw.println(prefix + "component=" + mActivityComponent.flattenToShortString()); pw.print(prefix); pw.print("task="); pw.println(getTask()); - pw.print(prefix); pw.print(" mFillsParent="); pw.print(mFillsParent); + pw.print(prefix); pw.print(" mOccludesParent="); pw.print(mOccludesParent); pw.print(" mOrientation="); pw.println(mOrientation); pw.println(prefix + "hiddenRequested=" + hiddenRequested + " mClientHidden=" + mClientHidden + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "") @@ -3152,7 +3174,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (mThumbnail != null){ mThumbnail.writeToProto(proto, THUMBNAIL); } - proto.write(FILLS_PARENT, mFillsParent); + proto.write(FILLS_PARENT, mOccludesParent); proto.write(APP_STOPPED, mAppStopped); proto.write(HIDDEN_REQUESTED, hiddenRequested); proto.write(CLIENT_HIDDEN, mClientHidden); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 282ed42468dd..410cc94869d8 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -295,7 +295,8 @@ public class DisplayRotation { false /* forceRelayout */); } - private void setUserRotation(int userRotationMode, int userRotation) { + @VisibleForTesting + void setUserRotation(int userRotationMode, int userRotation) { if (isDefaultDisplay) { // We'll be notified via settings listener, so we don't need to update internal values. final ContentResolver res = mContext.getContentResolver(); diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 207e8ef728eb..8507918df480 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -275,14 +275,14 @@ class DisplayWindowSettings { // This display used to be in freeform, but we don't support freeform anymore, so fall // back to fullscreen. if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM - && !mService.mSupportsFreeformWindowManagement) { + && !mService.mAtmService.mSupportsFreeformWindowManagement) { return WindowConfiguration.WINDOWING_MODE_FULLSCREEN; } // No record is present so use default windowing mode policy. if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { final boolean forceDesktopMode = mService.mForceDesktopModeOnExternalDisplays && displayId != Display.DEFAULT_DISPLAY; - windowingMode = mService.mSupportsFreeformWindowManagement + windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement && (mService.mIsPc || forceDesktopMode) ? WindowConfiguration.WINDOWING_MODE_FREEFORM : WindowConfiguration.WINDOWING_MODE_FULLSCREEN; diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index b1bc21977405..120ce3eb146e 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -355,7 +355,9 @@ public class DockedStackDividerController { void getTouchRegion(Rect outRegion) { outRegion.set(mTouchRegion); - outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top); + if (mWindow != null) { + outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top); + } } private void resetDragResizingChangeReported() { diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 4f0332c58deb..caa836376248 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -241,7 +241,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // Fetch all the surface controls and pass them to the client to get the animation // started. Cancel any existing recents animation running synchronously (do not hold the // WM lock) - mWindowManager.cancelRecentsAnimationSynchronously(REORDER_MOVE_TO_ORIGINAL_POSITION, + mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity"); mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner, this, mDefaultDisplay.mDisplayId, @@ -396,12 +396,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, @Override public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode, - boolean runSychronously, boolean sendUserLeaveHint) { - if (runSychronously) { - finishAnimation(reorderMode, sendUserLeaveHint); - } else { - mService.mH.post(() -> finishAnimation(reorderMode, sendUserLeaveHint)); - } + boolean sendUserLeaveHint) { + finishAnimation(reorderMode, sendUserLeaveHint); } @Override @@ -435,8 +431,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } else { // Just cancel directly to unleash from launcher when the next launching task is the // current top task. - mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE, - "stackOrderChanged"); + mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged"); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 724a72e1861e..8752f3796c58 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -168,8 +168,7 @@ public class RecentsAnimationController implements DeathRecipient { public interface RecentsAnimationCallbacks { /** Callback when recents animation is finished. */ - void onAnimationFinished(@ReorderMode int reorderMode, boolean runSychronously, - boolean sendUserLeaveHint); + void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint); } private final IRecentsAnimationController mController = @@ -221,8 +220,7 @@ public class RecentsAnimationController implements DeathRecipient { // prior to calling the callback mCallbacks.onAnimationFinished(moveHomeToTop ? REORDER_MOVE_TO_TOP - : REORDER_MOVE_TO_ORIGINAL_POSITION, - true /* runSynchronously */, sendUserLeaveHint); + : REORDER_MOVE_TO_ORIGINAL_POSITION, sendUserLeaveHint); mDisplayContent.mBoundsAnimationController.setAnimationType(FADE_IN); } finally { Binder.restoreCallingIdentity(token); @@ -498,21 +496,15 @@ public class RecentsAnimationController implements DeathRecipient { } void cancelAnimation(@ReorderMode int reorderMode, String reason) { - cancelAnimation(reorderMode, false /* runSynchronously */, false /*screenshot */, reason); - } - - void cancelAnimationSynchronously(@ReorderMode int reorderMode, String reason) { - cancelAnimation(reorderMode, true /* runSynchronously */, false /* screenshot */, reason); + cancelAnimation(reorderMode, false /*screenshot */, reason); } void cancelAnimationWithScreenshot(boolean screenshot) { - cancelAnimation(REORDER_KEEP_IN_PLACE, true /* sync */, screenshot, "stackOrderChanged"); + cancelAnimation(REORDER_KEEP_IN_PLACE, screenshot, "stackOrderChanged"); } - private void cancelAnimation(@ReorderMode int reorderMode, boolean runSynchronously, - boolean screenshot, String reason) { - if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason - + " runSynchronously=" + runSynchronously); + private void cancelAnimation(@ReorderMode int reorderMode, boolean screenshot, String reason) { + if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason); synchronized (mService.getWindowManagerLock()) { if (mCanceled) { // We've already canceled the animation @@ -525,16 +517,14 @@ public class RecentsAnimationController implements DeathRecipient { // Screen shot previous task when next task starts transition and notify the runner. // We will actually finish the animation once the runner calls cleanUpScreenshot(). final Task task = mPendingAnimations.get(0).mTask; - final TaskSnapshot taskSnapshot = screenshotRecentTask(task, reorderMode, - runSynchronously); + final TaskSnapshot taskSnapshot = screenshotRecentTask(task, reorderMode); try { mRunner.onAnimationCanceled(taskSnapshot); } catch (RemoteException e) { Slog.e(TAG, "Failed to cancel recents animation", e); } if (taskSnapshot == null) { - mCallbacks.onAnimationFinished(reorderMode, runSynchronously, - false /* sendUserLeaveHint */); + mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); } } else { // Otherwise, notify the runner and clean up the animation immediately @@ -545,8 +535,7 @@ public class RecentsAnimationController implements DeathRecipient { } catch (RemoteException e) { Slog.e(TAG, "Failed to cancel recents animation", e); } - mCallbacks.onAnimationFinished(reorderMode, runSynchronously, - false /* sendUserLeaveHint */); + mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); } } } @@ -592,8 +581,7 @@ public class RecentsAnimationController implements DeathRecipient { return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot; } - TaskSnapshot screenshotRecentTask(Task task, @ReorderMode int reorderMode, - boolean runSynchronously) { + TaskSnapshot screenshotRecentTask(Task task, @ReorderMode int reorderMode) { final TaskSnapshotController snapshotController = mService.mTaskSnapshotController; final ArraySet<Task> tasks = Sets.newArraySet(task); snapshotController.snapshotTasks(tasks); @@ -613,8 +601,7 @@ public class RecentsAnimationController implements DeathRecipient { if (DEBUG_RECENTS_ANIMATIONS) { Slog.d(TAG, "mRecentScreenshotAnimator finish"); } - mCallbacks.onAnimationFinished(reorderMode, runSynchronously, - false /* sendUserLeaveHint */); + mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); }, mService); mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator); return taskSnapshot; diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index 66d42db9b4dd..3401de629268 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -227,15 +227,10 @@ class RootActivityContainer extends ConfigurationContainer mStackSupervisor.mRootActivityContainer = this; } - @VisibleForTesting - void setWindowContainer(RootWindowContainer container) { - mRootWindowContainer = container; - mRootWindowContainer.setRootActivityContainer(this); - } - void setWindowManager(WindowManagerService wm) { mWindowManager = wm; - setWindowContainer(mWindowManager.mRoot); + mRootWindowContainer = mWindowManager.mRoot; + mRootWindowContainer.setRootActivityContainer(this); mDisplayManager = mService.mContext.getSystemService(DisplayManager.class); mDisplayManager.registerDisplayListener(this, mService.mUiHandler); mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); @@ -2266,7 +2261,7 @@ class RootActivityContainer extends ConfigurationContainer @WindowConfiguration.ActivityType int ignoreActivityType, @WindowConfiguration.WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed) { - mStackSupervisor.mRunningTasks.getTasks(maxNum, list, ignoreActivityType, + mStackSupervisor.getRunningTasks().getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, mActivityDisplays, callingUid, allowed); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index fd86faa6d035..3a2eb57f1d80 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -380,7 +380,7 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta boolean isResizeable() { return ActivityInfo.isResizeableMode(mResizeMode) || mSupportsPictureInPicture - || mWmService.mForceResizableTasks; + || mWmService.mAtmService.mForceResizableActivities; } /** diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 79367a050d46..cc2112ea5248 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -1666,7 +1666,7 @@ public class TaskStack extends WindowContainer<Task> implements * default bounds. */ Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) { - if (!mWmService.mSupportsPictureInPicture) { + if (!mWmService.mAtmService.mSupportsPictureInPicture) { return null; } @@ -1762,7 +1762,7 @@ public class TaskStack extends WindowContainer<Task> implements * Sets the current picture-in-picture aspect ratio. */ void setPictureInPictureAspectRatio(float aspectRatio) { - if (!mWmService.mSupportsPictureInPicture) { + if (!mWmService.mAtmService.mSupportsPictureInPicture) { return; } @@ -1792,7 +1792,7 @@ public class TaskStack extends WindowContainer<Task> implements * Sets the current picture-in-picture actions. */ void setPictureInPictureActions(List<RemoteAction> actions) { - if (!mWmService.mSupportsPictureInPicture) { + if (!mWmService.mAtmService.mSupportsPictureInPicture) { return; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index bbef261d17bb..29d232fc0511 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -767,11 +767,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken, @Nullable ConfigurationContainer requestingContainer) { - final boolean changed = mOrientation != orientation; - mOrientation = orientation; - if (!changed) { + if (mOrientation == orientation) { return; } + + mOrientation = orientation; final WindowContainer parent = getParent(); if (parent != null) { onDescendantOrientationChanged(freezeDisplayToken, requestingContainer); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 86faad0db965..d8d6841fe942 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -28,6 +28,7 @@ import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LE import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.content.pm.PackageManager.FEATURE_PC; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; import static android.os.Process.myPid; @@ -591,9 +592,6 @@ public class WindowManagerService extends IWindowManager.Stub int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; - boolean mForceResizableTasks; - boolean mSupportsPictureInPicture; - boolean mSupportsFreeformWindowManagement; boolean mIsPc; /** * Flag that indicates that desktop mode is forced for public secondary screens. @@ -819,7 +817,7 @@ public class WindowManagerService extends IWindowManager.Stub int mTransactionSequence; final WindowAnimator mAnimator; - final SurfaceAnimationRunner mSurfaceAnimationRunner; + SurfaceAnimationRunner mSurfaceAnimationRunner; /** * Keeps track of which animations got transferred to which animators. Entries will get cleaned @@ -957,6 +955,9 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>(); + @VisibleForTesting + final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener; + interface AppFreezeListener { void onAppFreezeTimeout(); } @@ -1010,6 +1011,7 @@ public class WindowManagerService extends IWindowManager.Stub mGlobalLock = atm.getGlobalLock(); mAtmService = atm; mContext = context; + mIsPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mAllowBootMessages = showBootMsgs; mOnlyCore = onlyCore; mLimitedAlphaCompositing = context.getResources().getBoolean( @@ -1159,26 +1161,28 @@ public class WindowManagerService extends IWindowManager.Stub mSystemGestureExcludedByPreQStickyImmersive = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, - new HandlerExecutor(mH), properties -> { - synchronized (mGlobalLock) { - final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP, - properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0)); - final boolean excludedByPreQSticky = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_WINDOW_MANAGER, - KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false); - if (mSystemGestureExcludedByPreQStickyImmersive != excludedByPreQSticky - || mSystemGestureExclusionLimitDp != exclusionLimitDp) { - mSystemGestureExclusionLimitDp = exclusionLimitDp; - mSystemGestureExcludedByPreQStickyImmersive = excludedByPreQSticky; - mRoot.forAllDisplays(DisplayContent::updateSystemGestureExclusionLimit); - } - mSystemGestureExclusionLogDebounceTimeoutMillis = - DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER, - KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS, 0); - } - }); + mPropertiesChangedListener = properties -> { + synchronized (mGlobalLock) { + final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP, + properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0)); + final boolean excludedByPreQSticky = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false); + if (mSystemGestureExcludedByPreQStickyImmersive != excludedByPreQSticky + || mSystemGestureExclusionLimitDp != exclusionLimitDp) { + mSystemGestureExclusionLimitDp = exclusionLimitDp; + mSystemGestureExcludedByPreQStickyImmersive = excludedByPreQSticky; + mRoot.forAllDisplays(DisplayContent::updateSystemGestureExclusionLimit); + } + + mSystemGestureExclusionLogDebounceTimeoutMillis = + DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS, 0); + } + }; + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + new HandlerExecutor(mH), mPropertiesChangedListener); LocalServices.addService(WindowManagerInternal.class, new LocalService()); } @@ -2595,7 +2599,7 @@ public class WindowManagerService extends IWindowManager.Stub mRecentsAnimationController = controller; } - public RecentsAnimationController getRecentsAnimationController() { + RecentsAnimationController getRecentsAnimationController() { return mRecentsAnimationController; } @@ -2603,74 +2607,37 @@ public class WindowManagerService extends IWindowManager.Stub * @return Whether the next recents animation can continue to start. Called from * {@link RecentsAnimation#startRecentsActivity}. */ - public boolean canStartRecentsAnimation() { - synchronized (mGlobalLock) { - // TODO(multi-display): currently only default display support recent activity - if (getDefaultDisplayContentLocked().mAppTransition.isTransitionSet()) { - return false; - } - return true; + boolean canStartRecentsAnimation() { + // TODO(multi-display): currently only default display support recent activity + if (getDefaultDisplayContentLocked().mAppTransition.isTransitionSet()) { + return false; } + return true; } - /** - * Cancels any running recents animation. The caller should NOT hold the WM lock while calling - * this method, as it will call back into AM and may cause a deadlock. Any locking will be done - * in the animation controller itself. - */ - public void cancelRecentsAnimationSynchronously( + void cancelRecentsAnimation( @RecentsAnimationController.ReorderMode int reorderMode, String reason) { if (mRecentsAnimationController != null) { // This call will call through to cleanupAnimation() below after the animation is // canceled - mRecentsAnimationController.cancelAnimationSynchronously(reorderMode, reason); - } - } - - public void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) { - synchronized (mGlobalLock) { - if (mRecentsAnimationController != null) { - final RecentsAnimationController controller = mRecentsAnimationController; - mRecentsAnimationController = null; - controller.cleanupAnimation(reorderMode); - // TODO(mult-display): currently only default display support recents animation. - getDefaultDisplayContentLocked().mAppTransition.updateBooster(); - } - } - } - - public void setAppFullscreen(IBinder token, boolean toOpaque) { - synchronized (mGlobalLock) { - final AppWindowToken atoken = mRoot.getAppWindowToken(token); - if (atoken != null) { - atoken.setFillsParent(toOpaque); - setWindowOpaqueLocked(token, toOpaque); - mWindowPlacerLocked.requestTraversal(); - } + mRecentsAnimationController.cancelAnimation(reorderMode, reason); } } - public void setWindowOpaque(IBinder token, boolean isOpaque) { - synchronized (mGlobalLock) { - setWindowOpaqueLocked(token, isOpaque); + void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) { + if (mRecentsAnimationController != null) { + final RecentsAnimationController controller = mRecentsAnimationController; + mRecentsAnimationController = null; + controller.cleanupAnimation(reorderMode); + // TODO(mult-display): currently only default display support recents animation. + getDefaultDisplayContentLocked().mAppTransition.updateBooster(); } } - private void setWindowOpaqueLocked(IBinder token, boolean isOpaque) { + void setWindowOpaqueLocked(IBinder token, boolean isOpaque) { final AppWindowToken wtoken = mRoot.getAppWindowToken(token); if (wtoken != null) { - final WindowState win = wtoken.findMainWindow(); - if (win == null) { - return; - } - isOpaque = isOpaque & !PixelFormat.formatHasAlpha(win.getAttrs().format); - win.mWinAnimator.setOpaqueLocked(isOpaque); - } - } - - public void setDockedStackCreateState(int mode, Rect bounds) { - synchronized (mGlobalLock) { - setDockedStackCreateStateLocked(mode, bounds); + wtoken.setMainWindowOpaque(isOpaque); } } @@ -2679,14 +2646,12 @@ public class WindowManagerService extends IWindowManager.Stub mDockedStackCreateBounds = bounds; } - public void checkSplitScreenMinimizedChanged(boolean animate) { - synchronized (mGlobalLock) { - final DisplayContent displayContent = getDefaultDisplayContentLocked(); - displayContent.getDockedDividerController().checkMinimizeChanged(animate); - } + void checkSplitScreenMinimizedChanged(boolean animate) { + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + displayContent.getDockedDividerController().checkMinimizeChanged(animate); } - public boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) { + boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio( aspectRatio); @@ -4847,8 +4812,10 @@ public class WindowManagerService extends IWindowManager.Stub case UPDATE_DOCKED_STACK_DIVIDER: { synchronized (mGlobalLock) { final DisplayContent displayContent = getDefaultDisplayContentLocked(); - displayContent.getDockedDividerController().reevaluateVisibility(false); - displayContent.adjustForImeIfNeeded(); + if (displayContent != null) { + displayContent.getDockedDividerController().reevaluateVisibility(false); + displayContent.adjustForImeIfNeeded(); + } } break; } @@ -6495,31 +6462,14 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void setForceResizableTasks(boolean forceResizableTasks) { - synchronized (mGlobalLock) { - mForceResizableTasks = forceResizableTasks; - } - } - - public void setSupportsPictureInPicture(boolean supportsPictureInPicture) { - synchronized (mGlobalLock) { - mSupportsPictureInPicture = supportsPictureInPicture; - } - } - - public void setSupportsFreeformWindowManagement(boolean supportsFreeformWindowManagement) { - synchronized (mGlobalLock) { - mSupportsFreeformWindowManagement = supportsFreeformWindowManagement; - } - } - void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) { synchronized (mGlobalLock) { mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays; } } - public void setIsPc(boolean isPc) { + @VisibleForTesting + void setIsPc(boolean isPc) { synchronized (mGlobalLock) { mIsPc = isPc; } @@ -6546,7 +6496,7 @@ public class WindowManagerService extends IWindowManager.Stub "registerPinnedStackListener()")) { return; } - if (!mSupportsPictureInPicture) { + if (!mAtmService.mSupportsPictureInPicture) { return; } synchronized (mGlobalLock) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 466ca9315f6f..03f475582a5a 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -165,6 +165,7 @@ static void loadSystemIconAsSpriteWithPointerIcon(JNIEnv* env, jobject contextOb outPointerIcon->bitmap.readPixels(bitmapCopy->info(), bitmapCopy->getPixels(), bitmapCopy->rowBytes(), 0, 0); } + outSpriteIcon->style = outPointerIcon->style; outSpriteIcon->hotSpotX = outPointerIcon->hotSpotX; outSpriteIcon->hotSpotY = outPointerIcon->hotSpotY; } @@ -1252,7 +1253,8 @@ void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) { status_t status = android_view_PointerIcon_load(env, pointerIconObj.get(), displayContext.get(), &pointerIcon); if (!status && !pointerIcon.isNullIcon()) { - *icon = SpriteIcon(pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY); + *icon = SpriteIcon( + pointerIcon.bitmap, pointerIcon.style, pointerIcon.hotSpotX, pointerIcon.hotSpotY); } else { *icon = SpriteIcon(); } @@ -1293,10 +1295,12 @@ void NativeInputManager::loadAdditionalMouseResources(std::map<int32_t, SpriteIc milliseconds_to_nanoseconds(pointerIcon.durationPerFrame); animationData.animationFrames.reserve(numFrames); animationData.animationFrames.push_back(SpriteIcon( - pointerIcon.bitmap, pointerIcon.hotSpotX, pointerIcon.hotSpotY)); + pointerIcon.bitmap, pointerIcon.style, + pointerIcon.hotSpotX, pointerIcon.hotSpotY)); for (size_t i = 0; i < numFrames - 1; ++i) { animationData.animationFrames.push_back(SpriteIcon( - pointerIcon.bitmapFrames[i], pointerIcon.hotSpotX, pointerIcon.hotSpotY)); + pointerIcon.bitmapFrames[i], pointerIcon.style, + pointerIcon.hotSpotX, pointerIcon.hotSpotY)); } } } @@ -1711,6 +1715,7 @@ static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */, pointerIcon.bitmap.readPixels(spriteInfo, spriteIcon.bitmap.getPixels(), spriteIcon.bitmap.rowBytes(), 0, 0); } + spriteIcon.style = pointerIcon.style; spriteIcon.hotSpotX = pointerIcon.hotSpotX; spriteIcon.hotSpotY = pointerIcon.hotSpotY; im->setCustomPointerIcon(spriteIcon); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ed900b15e8d8..67ae4070966d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -635,6 +635,13 @@ public final class SystemServer { SystemServerInitThreadPool.get().submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG); t.traceEnd(); + // Platform compat service is used by ActivityManagerService, PackageManagerService, and + // possibly others in the future. b/135010838. + t.traceBegin("PlatformCompat"); + ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, + new PlatformCompat(mSystemContext)); + t.traceEnd(); + // Wait for installd to finish starting up so that it has a chance to // create critical directories such as /data/user with the appropriate // permissions. We need this to complete before we initialize other services. @@ -1102,10 +1109,6 @@ public final class SystemServer { SignedConfigService.registerUpdateReceiver(mSystemContext); t.traceEnd(); - t.traceBegin("PlatformCompat"); - ServiceManager.addService("platform_compat", new PlatformCompat(context)); - t.traceEnd(); - } catch (RuntimeException e) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting core service", e); diff --git a/services/robotests/backup/src/com/android/server/backup/TrampolineRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java index f0a5d3749a13..a1bfcdf4bdfa 100644 --- a/services/robotests/backup/src/com/android/server/backup/TrampolineRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -26,8 +26,12 @@ import static com.android.server.backup.testing.TransportData.backupTransport; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; import static org.testng.Assert.expectThrows; @@ -72,7 +76,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -/** Tests for {@link com.android.server.backup.Trampoline}. */ +/** Tests for {@link BackupManagerService}. */ @RunWith(RobolectricTestRunner.class) @Config( shadows = { @@ -83,7 +87,7 @@ import java.io.StringWriter; ShadowSystemServiceRegistry.class }) @Presubmit -public class TrampolineRoboTest { +public class BackupManagerServiceRoboTest { private static final String TEST_PACKAGE = "package"; private static final String TEST_TRANSPORT = "transport"; private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE}; @@ -121,7 +125,7 @@ public class TrampolineRoboTest { /** Test that the service registers users. */ @Test public void testStartServiceForUser_registersUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); backupManagerService.setBackupServiceActive(mUserOneId, true); backupManagerService.startServiceForUser(mUserOneId); @@ -134,7 +138,7 @@ public class TrampolineRoboTest { /** Test that the service registers users. */ @Test public void testStartServiceForUser_withServiceInstance_registersUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); backupManagerService.setBackupServiceActive(mUserOneId, true); backupManagerService.startServiceForUser(mUserOneId, mUserOneService); @@ -147,7 +151,7 @@ public class TrampolineRoboTest { /** Test that the service unregisters users when stopped. */ @Test public void testStopServiceForUser_forRegisteredUser_unregistersCorrectUser() throws Exception { - Trampoline backupManagerService = + BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); ShadowBinder.setCallingUid(Process.SYSTEM_UID); @@ -163,7 +167,7 @@ public class TrampolineRoboTest { /** Test that the service unregisters users when stopped. */ @Test public void testStopServiceForUser_forRegisteredUser_tearsDownCorrectUser() throws Exception { - Trampoline backupManagerService = + BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); backupManagerService.setBackupServiceActive(mUserTwoId, true); backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); @@ -177,7 +181,7 @@ public class TrampolineRoboTest { /** Test that the service unregisters users when stopped. */ @Test public void testStopServiceForUser_forUnknownUser_doesNothing() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); backupManagerService.setBackupServiceActive(mUserOneId, true); ShadowBinder.setCallingUid(Process.SYSTEM_UID); @@ -194,7 +198,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testDataChanged_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -206,7 +210,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testDataChanged_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -218,7 +222,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testAgentConnected_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); IBinder agentBinder = mock(IBinder.class); @@ -231,7 +235,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testAgentConnected_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); IBinder agentBinder = mock(IBinder.class); @@ -244,7 +248,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testOpComplete_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -256,7 +260,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testOpComplete_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -272,7 +276,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testInitializeTransports_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); String[] transports = {TEST_TRANSPORT}; @@ -285,7 +289,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testInitializeTransports_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); String[] transports = {TEST_TRANSPORT}; @@ -298,7 +302,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testClearBackupData_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -310,7 +314,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testClearBackupData_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -322,7 +326,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testGetCurrentTransport_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -334,7 +338,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetCurrentTransport_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -347,7 +351,7 @@ public class TrampolineRoboTest { @Test public void testGetCurrentTransportComponent_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -360,7 +364,7 @@ public class TrampolineRoboTest { @Test public void testGetCurrentTransportComponent_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -372,7 +376,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testListAllTransports_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -384,7 +388,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testListAllTransports_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -397,7 +401,7 @@ public class TrampolineRoboTest { @Test public void testListAllTransportComponents_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -410,7 +414,7 @@ public class TrampolineRoboTest { @Test public void testListAllTransportComponents_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -422,7 +426,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testSelectBackupTransport_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -434,7 +438,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testSelectBackupTransport_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -446,7 +450,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testSelectTransportAsync_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); @@ -463,7 +467,7 @@ public class TrampolineRoboTest { @Test public void testSelectBackupTransportAsync_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); @@ -479,7 +483,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testGetConfigurationIntent_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -491,7 +495,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetConfigurationIntent_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -503,7 +507,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testGetDestinationString_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -515,7 +519,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetDestinationString_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -527,7 +531,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testGetDataManagementIntent_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -539,7 +543,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetDataManagementIntent_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -551,7 +555,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testGetDataManagementLabel_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -563,7 +567,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetDataManagementLabel_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -576,7 +580,7 @@ public class TrampolineRoboTest { @Test public void testUpdateTransportAttributes_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); @@ -606,7 +610,7 @@ public class TrampolineRoboTest { @Test public void testUpdateTransportAttributes_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); @@ -642,7 +646,7 @@ public class TrampolineRoboTest { */ @Test public void testSetBackupEnabled_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -657,7 +661,7 @@ public class TrampolineRoboTest { */ @Test public void testSetBackupEnabled_withPermission_propagatesForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); @@ -671,7 +675,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testSetBackupEnabled_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -683,7 +687,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testSetBackupEnabled_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -695,7 +699,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testSetAutoRestore_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -707,7 +711,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testSetAutoRestore_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -719,7 +723,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testIsBackupEnabled_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -731,7 +735,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testIsBackupEnabled_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -747,7 +751,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testIsAppEligibleForBackup_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -759,7 +763,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testIsAppEligibleForBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -772,7 +776,7 @@ public class TrampolineRoboTest { @Test public void testFilterAppsEligibleForBackup_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; @@ -786,7 +790,7 @@ public class TrampolineRoboTest { @Test public void testFilterAppsEligibleForBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; @@ -802,7 +806,7 @@ public class TrampolineRoboTest { */ @Test public void testBackupNow_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -815,7 +819,7 @@ public class TrampolineRoboTest { */ @Test public void testBackupNow_withPermission_propagatesForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); @@ -829,7 +833,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testBackupNow_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -841,7 +845,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testBackupNow_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -856,7 +860,7 @@ public class TrampolineRoboTest { */ @Test public void testRequestBackup_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; @@ -876,7 +880,7 @@ public class TrampolineRoboTest { */ @Test public void testRequestBackup_withPermission_propagatesForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); @@ -893,7 +897,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testRequestBackup_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); String[] packages = {TEST_PACKAGE}; IBackupObserver observer = mock(IBackupObserver.class); @@ -908,7 +912,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testRequestBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); String[] packages = {TEST_PACKAGE}; IBackupObserver observer = mock(IBackupObserver.class); @@ -926,7 +930,7 @@ public class TrampolineRoboTest { */ @Test public void testCancelBackups_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -939,7 +943,7 @@ public class TrampolineRoboTest { */ @Test public void testCancelBackups_withPermission_propagatesForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); @@ -952,7 +956,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testCancelBackups_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -964,7 +968,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testCancelBackups_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -976,7 +980,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testBeginFullBackup_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService); FullBackupJob job = new FullBackupJob(); @@ -988,7 +992,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testBeginFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); FullBackupJob job = new FullBackupJob(); backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job); @@ -999,7 +1003,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testEndFullBackup_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService); backupManagerService.endFullBackup(UserHandle.USER_SYSTEM); @@ -1010,7 +1014,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testEndFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); backupManagerService.endFullBackup(UserHandle.USER_SYSTEM); @@ -1020,7 +1024,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testFullTransportBackup_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; @@ -1033,7 +1037,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testFullTransportBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; @@ -1050,7 +1054,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testRestoreAtInstall_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1062,7 +1066,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testRestoreAtInstall_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -1074,7 +1078,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testBeginRestoreSession_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1086,7 +1090,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testBeginRestoreSession_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -1099,7 +1103,7 @@ public class TrampolineRoboTest { @Test public void testGetAvailableRestoreToken_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1111,7 +1115,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetAvailableRestoreToken_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -1127,7 +1131,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testSetBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService); ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM)); @@ -1139,7 +1143,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testSetBackupPassword_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); backupManagerService.setBackupPassword("currentPassword", "newPassword"); @@ -1149,7 +1153,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testHasBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserOneService); ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM)); @@ -1161,7 +1165,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testHasBackupPassword_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); backupManagerService.hasBackupPassword(); @@ -1174,7 +1178,7 @@ public class TrampolineRoboTest { */ @Test public void testAdbBackup_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1202,7 +1206,7 @@ public class TrampolineRoboTest { */ @Test public void testAdbBackup_withPermission_propagatesForNonCallingUser() throws Exception { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); @@ -1239,7 +1243,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testAdbBackup_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1274,7 +1278,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testAdbBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -1312,7 +1316,7 @@ public class TrampolineRoboTest { */ @Test public void testAdbRestore_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1327,7 +1331,7 @@ public class TrampolineRoboTest { */ @Test public void testAdbRestore_withPermission_propagatesForNonCallingUser() throws Exception { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); registerUser(backupManagerService, mUserTwoId, mUserTwoService); ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); @@ -1341,7 +1345,7 @@ public class TrampolineRoboTest { /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testAdbRestore_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1354,7 +1358,7 @@ public class TrampolineRoboTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testAdbRestore_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest(); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -1374,7 +1378,7 @@ public class TrampolineRoboTest { @Test public void testAcknowledgeAdbBackupOrRestore_onRegisteredUser_callsMethodForUser() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class); @@ -1400,7 +1404,7 @@ public class TrampolineRoboTest { @Test public void testAcknowledgeAdbBackupOrRestore_onUnknownUser_doesNotPropagateCall() throws Exception { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class); @@ -1430,7 +1434,7 @@ public class TrampolineRoboTest { @Test public void testDump_onRegisteredUser_callsMethodForUser() throws Exception { grantDumpPermissions(); - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); File testFile = createTestFile(); FileDescriptor fileDescriptor = new FileDescriptor(); PrintWriter printWriter = new PrintWriter(testFile); @@ -1446,7 +1450,7 @@ public class TrampolineRoboTest { @Test public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception { grantDumpPermissions(); - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); File testFile = createTestFile(); FileDescriptor fileDescriptor = new FileDescriptor(); PrintWriter printWriter = new PrintWriter(testFile); @@ -1461,7 +1465,7 @@ public class TrampolineRoboTest { @Test public void testDump_users_dumpsListOfRegisteredUsers() { grantDumpPermissions(); - Trampoline backupManagerService = createSystemRegisteredService(); + BackupManagerService backupManagerService = createSystemRegisteredService(); registerUser(backupManagerService, mUserOneId, mUserOneService); StringWriter out = new StringWriter(); PrintWriter writer = new PrintWriter(out); @@ -1471,7 +1475,7 @@ public class TrampolineRoboTest { writer.flush(); assertEquals( - String.format("%s %d %d\n", Trampoline.DUMP_RUNNING_USERS_MESSAGE, + String.format("%s %d %d\n", BackupManagerService.DUMP_RUNNING_USERS_MESSAGE, UserHandle.USER_SYSTEM, mUserOneId), out.toString()); } @@ -1493,7 +1497,7 @@ public class TrampolineRoboTest { */ @Test public void testGetServiceForUser_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); @@ -1510,7 +1514,7 @@ public class TrampolineRoboTest { */ @Test public void testGetServiceForUserIfCallerHasPermission_withPermission_worksForNonCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ true); @@ -1525,7 +1529,7 @@ public class TrampolineRoboTest { */ @Test public void testGetServiceForUserIfCallerHasPermission_withoutPermission_worksForCallingUser() { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); registerUser(backupManagerService, mUserOneId, mUserOneService); setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); @@ -1534,25 +1538,98 @@ public class TrampolineRoboTest { backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test")); } - private Trampoline createService() { - return new Trampoline(mContext); + /** + * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. This is + * specifically to prevent overloading the logs in production. + */ + @Test + public void testMoreDebug_isFalse() throws Exception { + boolean moreDebug = BackupManagerService.MORE_DEBUG; + + assertThat(moreDebug).isFalse(); + } + + /** Test that the constructor handles {@code null} parameters. */ + @Test + public void testConstructor_withNullContext_throws() throws Exception { + expectThrows( + NullPointerException.class, + () -> + new BackupManagerService( + /* context */ null, + new SparseArray<>())); } - private Trampoline createSystemRegisteredService() { - Trampoline trampoline = createService(); - registerUser(trampoline, UserHandle.USER_SYSTEM, mUserSystemService); - return trampoline; + /** Test that the constructor does not create {@link UserBackupManagerService} instances. */ + @Test + public void testConstructor_doesNotRegisterUsers() throws Exception { + BackupManagerService backupManagerService = createService(); + + assertThat(backupManagerService.getUserServices().size()).isEqualTo(0); + } + + // --------------------------------------------- + // Lifecycle tests + // --------------------------------------------- + + /** testOnStart_publishesService */ + @Test + public void testOnStart_publishesService() { + BackupManagerService backupManagerService = mock(BackupManagerService.class); + BackupManagerService.Lifecycle lifecycle = + spy(new BackupManagerService.Lifecycle(mContext, backupManagerService)); + doNothing().when(lifecycle).publishService(anyString(), any()); + + lifecycle.onStart(); + + verify(lifecycle).publishService(Context.BACKUP_SERVICE, backupManagerService); + } + + /** testOnUnlockUser_forwards */ + @Test + public void testOnUnlockUser_forwards() { + BackupManagerService backupManagerService = mock(BackupManagerService.class); + BackupManagerService.Lifecycle lifecycle = + new BackupManagerService.Lifecycle(mContext, backupManagerService); + + lifecycle.onUnlockUser(UserHandle.USER_SYSTEM); + + verify(backupManagerService).onUnlockUser(UserHandle.USER_SYSTEM); + } + + /** testOnStopUser_forwards */ + @Test + public void testOnStopUser_forwards() { + BackupManagerService backupManagerService = mock(BackupManagerService.class); + BackupManagerService.Lifecycle lifecycle = + new BackupManagerService.Lifecycle(mContext, backupManagerService); + + lifecycle.onStopUser(UserHandle.USER_SYSTEM); + + verify(backupManagerService).onStopUser(UserHandle.USER_SYSTEM); + } + + private BackupManagerService createService() { + return new BackupManagerService(mContext); + } + + private BackupManagerService createSystemRegisteredService() { + BackupManagerService backupManagerService = createService(); + registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserSystemService); + return backupManagerService; } private void registerUser( - Trampoline trampoline, int userId, UserBackupManagerService userBackupManagerService) { - trampoline.setBackupServiceActive(userId, true); - trampoline.startServiceForUser(userId, userBackupManagerService); + BackupManagerService backupManagerService, + int userId, + UserBackupManagerService userBackupManagerService) { + backupManagerService.setBackupServiceActive(userId, true); + backupManagerService.startServiceForUser(userId, userBackupManagerService); } - private Trampoline createServiceAndRegisterUser( + private BackupManagerService createServiceAndRegisterUser( int userId, UserBackupManagerService userBackupManagerService) { - Trampoline backupManagerService = createService(); + BackupManagerService backupManagerService = createService(); backupManagerService.setBackupServiceActive(userBackupManagerService.getUserId(), true); backupManagerService.startServiceForUser(userId, userBackupManagerService); return backupManagerService; diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java deleted file mode 100644 index a034474837a5..000000000000 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.backup; - -import static android.Manifest.permission.BACKUP; -import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.robolectric.Shadows.shadowOf; -import static org.testng.Assert.expectThrows; - -import android.annotation.UserIdInt; -import android.app.Application; -import android.content.Context; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.os.UserHandle; -import android.os.UserManager; -import android.platform.test.annotations.Presubmit; -import android.util.SparseArray; - -import com.android.server.testing.shadows.ShadowApplicationPackageManager; -import com.android.server.testing.shadows.ShadowBinder; -import com.android.server.testing.shadows.ShadowEnvironment; -import com.android.server.testing.shadows.ShadowSystemServiceRegistry; -import com.android.server.testing.shadows.ShadowUserManager; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowContextWrapper; - -import java.io.File; - -/** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */ -@RunWith(RobolectricTestRunner.class) -@Config( - shadows = { - ShadowApplicationPackageManager.class, - ShadowBinder.class, - ShadowEnvironment.class, - ShadowSystemServiceRegistry.class, - ShadowUserManager.class, - }) -@Presubmit -public class BackupManagerServiceTest { - private static final String TEST_PACKAGE = "package"; - private static final String TEST_TRANSPORT = "transport"; - private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE}; - - private ShadowContextWrapper mShadowContext; - private ShadowUserManager mShadowUserManager; - private Context mContext; - private Trampoline mTrampoline; - @UserIdInt private int mUserOneId; - @UserIdInt private int mUserTwoId; - @Mock private UserBackupManagerService mUserOneService; - @Mock private UserBackupManagerService mUserTwoService; - - /** Initialize {@link BackupManagerService}. */ - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - Application application = RuntimeEnvironment.application; - mContext = application; - mShadowContext = shadowOf(application); - mShadowUserManager = Shadow.extract(UserManager.get(application)); - - mUserOneId = UserHandle.USER_SYSTEM + 1; - mUserTwoId = mUserOneId + 1; - mShadowUserManager.addUser(mUserOneId, "mUserOneId", 0); - mShadowUserManager.addUser(mUserTwoId, "mUserTwoId", 0); - - mShadowContext.grantPermissions(BACKUP); - mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL); - - mTrampoline = new Trampoline(mContext); - ShadowBinder.setCallingUid(Process.SYSTEM_UID); - } - - /** - * Clean up and reset state that was created for testing {@link BackupManagerService} - * operations. - */ - @After - public void tearDown() throws Exception { - ShadowBinder.reset(); - } - - /** - * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. This is - * specifically to prevent overloading the logs in production. - */ - @Test - public void testMoreDebug_isFalse() throws Exception { - boolean moreDebug = BackupManagerService.MORE_DEBUG; - - assertThat(moreDebug).isFalse(); - } - - /** Test that the constructor does not create {@link UserBackupManagerService} instances. */ - @Test - public void testConstructor_doesNotRegisterUsers() throws Exception { - BackupManagerService backupManagerService = createService(); - - assertThat(mTrampoline.getUserServices().size()).isEqualTo(0); - } - - /** Test that the constructor handles {@code null} parameters. */ - @Test - public void testConstructor_withNullContext_throws() throws Exception { - expectThrows( - NullPointerException.class, - () -> - new BackupManagerService( - /* context */ null, - new Trampoline(mContext), - new SparseArray<>())); - } - - /** Test that the constructor handles {@code null} parameters. */ - @Test - public void testConstructor_withNullTrampoline_throws() throws Exception { - expectThrows( - NullPointerException.class, - () -> - new BackupManagerService( - mContext, /* trampoline */ null, new SparseArray<>())); - } - - // --------------------------------------------- - // Lifecycle tests - // --------------------------------------------- - - /** testOnStart_publishesService */ - @Test - public void testOnStart_publishesService() { - Trampoline trampoline = mock(Trampoline.class); - BackupManagerService.Lifecycle lifecycle = - spy(new BackupManagerService.Lifecycle(mContext, trampoline)); - doNothing().when(lifecycle).publishService(anyString(), any()); - - lifecycle.onStart(); - - verify(lifecycle).publishService(Context.BACKUP_SERVICE, trampoline); - } - - /** testOnUnlockUser_forwards */ - @Test - public void testOnUnlockUser_forwards() { - Trampoline trampoline = mock(Trampoline.class); - BackupManagerService.Lifecycle lifecycle = - new BackupManagerService.Lifecycle(mContext, trampoline); - - lifecycle.onUnlockUser(UserHandle.USER_SYSTEM); - - verify(trampoline).onUnlockUser(UserHandle.USER_SYSTEM); - } - - /** testOnStopUser_forwards */ - @Test - public void testOnStopUser_forwards() { - Trampoline trampoline = mock(Trampoline.class); - BackupManagerService.Lifecycle lifecycle = - new BackupManagerService.Lifecycle(mContext, trampoline); - - lifecycle.onStopUser(UserHandle.USER_SYSTEM); - - verify(trampoline).onStopUser(UserHandle.USER_SYSTEM); - } - - private BackupManagerService createService() { - mShadowContext.grantPermissions(BACKUP); - return new BackupManagerService(mContext, mTrampoline, mTrampoline.getUserServices()); - } - - private void registerUser(int userId, UserBackupManagerService userBackupManagerService) { - mTrampoline.setBackupServiceActive(userId, true); - mTrampoline.startServiceForUser(userId, userBackupManagerService); - } - - /** - * Sets the calling user to {@code userId} and grants the permission INTERACT_ACROSS_USERS_FULL - * to the caller if {@code shouldGrantPermission} is {@code true}, else it denies the - * permission. - */ - private void setCallerAndGrantInteractUserPermission( - @UserIdInt int userId, boolean shouldGrantPermission) { - ShadowBinder.setCallingUserHandle(UserHandle.of(userId)); - if (shouldGrantPermission) { - mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL); - } else { - mShadowContext.denyPermissions(INTERACT_ACROSS_USERS_FULL); - } - } - - private ParcelFileDescriptor getFileDescriptorForAdbTest() throws Exception { - File testFile = new File(mContext.getFilesDir(), "test"); - testFile.createNewFile(); - return ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE); - } -} diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index 84e810dbb325..8632ca4c2898 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -1005,7 +1005,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService.createAndInitializeService( USER_ID, mContext, - new Trampoline(mContext), + new BackupManagerService(mContext), mBackupThread, mBaseStateDir, mDataDir, @@ -1026,7 +1026,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService.createAndInitializeService( USER_ID, mContext, - new Trampoline(mContext), + new BackupManagerService(mContext), mBackupThread, mBaseStateDir, mDataDir, @@ -1045,7 +1045,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService.createAndInitializeService( USER_ID, /* context */ null, - new Trampoline(mContext), + new BackupManagerService(mContext), mBackupThread, mBaseStateDir, mDataDir, @@ -1077,7 +1077,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService.createAndInitializeService( USER_ID, mContext, - new Trampoline(mContext), + new BackupManagerService(mContext), /* backupThread */ null, mBaseStateDir, mDataDir, @@ -1093,7 +1093,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService.createAndInitializeService( USER_ID, mContext, - new Trampoline(mContext), + new BackupManagerService(mContext), mBackupThread, /* baseStateDir */ null, mDataDir, @@ -1102,8 +1102,8 @@ public class UserBackupManagerServiceTest { /** * Test checking non-null argument on {@link - * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread, - * File, File, TransportManager)}. + * UserBackupManagerService#createAndInitializeService(int, Context, BackupManagerService, + * HandlerThread, File, File, TransportManager)}. */ @Test public void testCreateAndInitializeService_withNullDataDir_throws() { @@ -1113,7 +1113,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService.createAndInitializeService( USER_ID, mContext, - new Trampoline(mContext), + new BackupManagerService(mContext), mBackupThread, mBaseStateDir, /* dataDir */ null, @@ -1122,8 +1122,8 @@ public class UserBackupManagerServiceTest { /** * Test checking non-null argument on {@link - * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread, - * File, File, TransportManager)}. + * UserBackupManagerService#createAndInitializeService(int, Context, BackupManagerService, + * HandlerThread, File, File, TransportManager)}. */ @Test public void testCreateAndInitializeService_withNullTransportManager_throws() { @@ -1133,7 +1133,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService.createAndInitializeService( USER_ID, mContext, - new Trampoline(mContext), + new BackupManagerService(mContext), mBackupThread, mBaseStateDir, mDataDir, @@ -1151,7 +1151,7 @@ public class UserBackupManagerServiceTest { UserBackupManagerService service = UserBackupManagerService.createAndInitializeService( USER_ID, contextSpy, - new Trampoline(mContext), + new BackupManagerService(mContext), mBackupThread, mBaseStateDir, mDataDir, diff --git a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java index 392d182328a5..84421ef178c9 100644 --- a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java @@ -37,7 +37,7 @@ import android.os.Process; import android.util.Log; import com.android.server.backup.BackupAgentTimeoutParameters; -import com.android.server.backup.Trampoline; +import com.android.server.backup.BackupManagerService; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; @@ -89,7 +89,7 @@ public class BackupManagerServiceTestUtils { UserBackupManagerService.createAndInitializeService( userId, context, - new Trampoline(context), + new BackupManagerService(context), backupThread, baseStateDir, dataDir, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java index 3614763fecab..5d041b7c5757 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java @@ -71,6 +71,7 @@ public class TimeControllerTest { private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final int SOURCE_USER_ID = 0; + private TimeController.TcConstants mConstants; private TimeController mTimeController; private MockitoSession mMockingSession; @@ -110,6 +111,7 @@ public class TimeControllerTest { // Initialize real objects. mTimeController = new TimeController(mJobSchedulerService); + mConstants = mTimeController.getTcConstants(); spyOn(mTimeController); } @@ -528,6 +530,46 @@ public class TimeControllerTest { } @Test + public void testJobDelayWakeupAlarmToggling() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + + JobStatus job = createJobStatus( + "testMaybeStartTrackingJobLocked_DeadlineReverseOrder", + createJob().setMinimumLatency(HOUR_IN_MILLIS)); + + doReturn(true).when(mTimeController) + .wouldBeReadyWithConstraintLocked(eq(job), anyInt()); + + // Starting off with using a wakeup alarm. + mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false; + InOrder inOrder = inOrder(mAlarmManager); + + mTimeController.maybeStartTrackingJobLocked(job, null); + inOrder.verify(mAlarmManager, times(1)) + .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(), + anyLong(), + eq(TAG_DELAY), any(), any(), any()); + + // Use a non wakeup alarm. + mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = true; + + mTimeController.maybeStartTrackingJobLocked(job, null); + inOrder.verify(mAlarmManager, times(1)) + .set(eq(AlarmManager.ELAPSED_REALTIME), eq(now + HOUR_IN_MILLIS), anyLong(), + anyLong(), eq(TAG_DELAY), + any(), any(), any()); + + // Back off, use a wakeup alarm. + mConstants.USE_NON_WAKEUP_ALARM_FOR_DELAY = false; + + mTimeController.maybeStartTrackingJobLocked(job, null); + inOrder.verify(mAlarmManager, times(1)) + .set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(now + HOUR_IN_MILLIS), anyLong(), + anyLong(), + eq(TAG_DELAY), any(), any(), any()); + } + + @Test public void testCheckExpiredDeadlinesAndResetAlarm_AllReady() { doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 1084d625f8a3..939aafa813e6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -752,7 +752,7 @@ public class AbstractAccessibilityServiceConnectionTest { } @Override - protected boolean isCalledForCurrentUserLocked() { + protected boolean hasRightsToCurrentUserLocked() { return mResolvedUserId == mSystemSupport.getCurrentUserIdLocked(); } diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index 848ef4545034..68b413ff976c 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.server.backup; @@ -76,7 +76,7 @@ import java.util.concurrent.TimeUnit; @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) -public class TrampolineTest { +public class BackupManagerServiceTest { private static final String PACKAGE_NAME = "some.package.name"; private static final String TRANSPORT_NAME = "some.transport.name"; private static final String CURRENT_PASSWORD = "current_password"; @@ -100,8 +100,6 @@ public class TrampolineTest { @UserIdInt private int mUserId; @Mock - private BackupManagerService mBackupManagerServiceMock; - @Mock private UserBackupManagerService mUserBackupManagerService; @Mock private Context mContextMock; @@ -124,7 +122,7 @@ public class TrampolineTest { private FileDescriptor mFileDescriptorStub = new FileDescriptor(); - private TrampolineTestable mTrampoline; + private BackupManagerServiceTestable mService; private File mTestDir; private File mSuppressFile; private SparseArray<UserBackupManagerService> mUserServices; @@ -142,38 +140,37 @@ public class TrampolineTest { when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock); when(mUserManagerMock.getUserInfo(UNSTARTED_NON_USER_SYSTEM)).thenReturn(mUserInfoMock); - TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock; - TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM; - TrampolineTestable.sCallingUid = Process.SYSTEM_UID; - TrampolineTestable.sBackupDisabled = false; - TrampolineTestable.sUserManagerMock = mUserManagerMock; + BackupManagerServiceTestable.sCallingUserId = UserHandle.USER_SYSTEM; + BackupManagerServiceTestable.sCallingUid = Process.SYSTEM_UID; + BackupManagerServiceTestable.sBackupDisabled = false; + BackupManagerServiceTestable.sUserManagerMock = mUserManagerMock; mTestDir = InstrumentationRegistry.getContext().getFilesDir(); mTestDir.mkdirs(); mSuppressFile = new File(mTestDir, "suppress"); - TrampolineTestable.sSuppressFile = mSuppressFile; + BackupManagerServiceTestable.sSuppressFile = mSuppressFile; setUpStateFilesForNonSystemUser(NON_USER_SYSTEM); setUpStateFilesForNonSystemUser(UNSTARTED_NON_USER_SYSTEM); when(mContextMock.getSystemService(Context.JOB_SCHEDULER_SERVICE)) .thenReturn(mock(JobScheduler.class)); - mTrampoline = new TrampolineTestable(mContextMock, mUserServices); + mService = new BackupManagerServiceTestable(mContextMock, mUserServices); } private void setUpStateFilesForNonSystemUser(int userId) { File activatedFile = new File(mTestDir, "activate-" + userId); - TrampolineTestable.sActivatedFiles.append(userId, activatedFile); + BackupManagerServiceTestable.sActivatedFiles.append(userId, activatedFile); File rememberActivatedFile = new File(mTestDir, "rem-activate-" + userId); - TrampolineTestable.sRememberActivatedFiles.append(userId, rememberActivatedFile); + BackupManagerServiceTestable.sRememberActivatedFiles.append(userId, rememberActivatedFile); } @After public void tearDown() throws Exception { mSuppressFile.delete(); - deleteFiles(TrampolineTestable.sActivatedFiles); - deleteFiles(TrampolineTestable.sRememberActivatedFiles); + deleteFiles(BackupManagerServiceTestable.sActivatedFiles); + deleteFiles(BackupManagerServiceTestable.sRememberActivatedFiles); } private void deleteFiles(SparseArray<File> files) { @@ -185,144 +182,149 @@ public class TrampolineTest { @Test public void testIsBackupServiceActive_whenBackupsNotDisabledAndSuppressFileDoesNotExist() { - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void testOnUnlockUser_forNonSystemUserWhenBackupsDisabled_doesNotStartUser() { - TrampolineTestable.sBackupDisabled = true; - TrampolineTestable trampoline = new TrampolineTestable(mContextMock, new SparseArray<>()); + BackupManagerServiceTestable.sBackupDisabled = true; + BackupManagerServiceTestable service = + new BackupManagerServiceTestable(mContextMock, new SparseArray<>()); ConditionVariable unlocked = new ConditionVariable(false); - trampoline.onUnlockUser(NON_USER_SYSTEM); + service.onUnlockUser(NON_USER_SYSTEM); - trampoline.getBackupHandler().post(unlocked::open); + service.getBackupHandler().post(unlocked::open); unlocked.block(); - assertNull(trampoline.getUserService(NON_USER_SYSTEM)); + assertNull(service.getUserService(NON_USER_SYSTEM)); } @Test public void testOnUnlockUser_forSystemUserWhenBackupsDisabled_doesNotStartUser() { - TrampolineTestable.sBackupDisabled = true; - TrampolineTestable trampoline = new TrampolineTestable(mContextMock, new SparseArray<>()); + BackupManagerServiceTestable.sBackupDisabled = true; + BackupManagerServiceTestable service = + new BackupManagerServiceTestable(mContextMock, new SparseArray<>()); ConditionVariable unlocked = new ConditionVariable(false); - trampoline.onUnlockUser(UserHandle.USER_SYSTEM); + service.onUnlockUser(UserHandle.USER_SYSTEM); - trampoline.getBackupHandler().post(unlocked::open); + service.getBackupHandler().post(unlocked::open); unlocked.block(); - assertNull(trampoline.getUserService(UserHandle.USER_SYSTEM)); + assertNull(service.getUserService(UserHandle.USER_SYSTEM)); } @Test public void testOnUnlockUser_whenBackupNotActivated_doesNotStartUser() { - TrampolineTestable.sBackupDisabled = false; - TrampolineTestable trampoline = new TrampolineTestable(mContextMock, new SparseArray<>()); - trampoline.setBackupServiceActive(NON_USER_SYSTEM, false); + BackupManagerServiceTestable.sBackupDisabled = false; + BackupManagerServiceTestable service = + new BackupManagerServiceTestable(mContextMock, new SparseArray<>()); + service.setBackupServiceActive(NON_USER_SYSTEM, false); ConditionVariable unlocked = new ConditionVariable(false); - trampoline.onUnlockUser(NON_USER_SYSTEM); + service.onUnlockUser(NON_USER_SYSTEM); - trampoline.getBackupHandler().post(unlocked::open); + service.getBackupHandler().post(unlocked::open); unlocked.block(); - assertNull(trampoline.getUserService(NON_USER_SYSTEM)); + assertNull(service.getUserService(NON_USER_SYSTEM)); } @Test public void testIsBackupServiceActive_forSystemUserWhenBackupDisabled_returnsTrue() throws Exception { - TrampolineTestable.sBackupDisabled = true; - Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices); - trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + BackupManagerServiceTestable.sBackupDisabled = true; + BackupManagerService backupManagerService = + new BackupManagerServiceTestable(mContextMock, mUserServices); + backupManagerService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(backupManagerService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void testIsBackupServiceActive_forNonSystemUserWhenBackupDisabled_returnsTrue() throws Exception { - TrampolineTestable.sBackupDisabled = true; - Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices); - trampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + BackupManagerServiceTestable.sBackupDisabled = true; + BackupManagerService backupManagerService = + new BackupManagerServiceTestable(mContextMock, mUserServices); + backupManagerService.setBackupServiceActive(NON_USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(backupManagerService.isBackupServiceActive(NON_USER_SYSTEM)); } @Test public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated() throws Exception { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(mService.isBackupServiceActive(NON_USER_SYSTEM)); } @Test public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated() throws Exception { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); // Don't activate non-system user. - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(mService.isBackupServiceActive(NON_USER_SYSTEM)); } @Test public void isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated() - throws Exception { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + throws Exception { + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM)); } @Test public void isBackupServiceActive_forUnstartedNonSystemUser_returnsTrueWhenSystemAndUserActivated() throws Exception { - mTrampoline.setBackupServiceActive(UNSTARTED_NON_USER_SYSTEM, true); + mService.setBackupServiceActive(UNSTARTED_NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UNSTARTED_NON_USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(UNSTARTED_NON_USER_SYSTEM)); } @Test public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() { - TrampolineTestable.sCallingUid = Process.SYSTEM_UID; + BackupManagerServiceTestable.sCallingUid = Process.SYSTEM_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() { - TrampolineTestable.sCallingUid = Process.ROOT_UID; + BackupManagerServiceTestable.sCallingUid = Process.ROOT_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() { - TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; + BackupManagerServiceTestable.sCallingUid = Process.FIRST_APPLICATION_UID; try { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); fail(); } catch (SecurityException expected) { } @@ -331,30 +333,30 @@ public class TrampolineTest { @Test public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() { when(mUserInfoMock.isManagedProfile()).thenReturn(true); - TrampolineTestable.sCallingUid = Process.SYSTEM_UID; + BackupManagerServiceTestable.sCallingUid = Process.SYSTEM_UID; - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM)); } @Test public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() { when(mUserInfoMock.isManagedProfile()).thenReturn(true); - TrampolineTestable.sCallingUid = Process.ROOT_UID; + BackupManagerServiceTestable.sCallingUid = Process.ROOT_UID; - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM)); } @Test public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() { when(mUserInfoMock.isManagedProfile()).thenReturn(true); - TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; + BackupManagerServiceTestable.sCallingUid = Process.FIRST_APPLICATION_UID; try { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); fail(); } catch (SecurityException expected) { } @@ -367,7 +369,7 @@ public class TrampolineTest { .enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString()); try { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); fail(); } catch (SecurityException expected) { } @@ -381,7 +383,7 @@ public class TrampolineTest { eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString()); try { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); fail(); } catch (SecurityException expected) { } @@ -389,95 +391,96 @@ public class TrampolineTest { @Test public void setBackupServiceActive_backupDisabled_ignored() { - TrampolineTestable.sBackupDisabled = true; - TrampolineTestable trampoline = new TrampolineTestable(mContextMock, mUserServices); + BackupManagerServiceTestable.sBackupDisabled = true; + BackupManagerServiceTestable service = + new BackupManagerServiceTestable(mContextMock, mUserServices); - trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + service.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(service.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void setBackupServiceActive_alreadyActive_ignored() { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void setBackupServiceActive_makeNonActive_serviceDeletedAndSuppressFileCreated() throws IOException { - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(mService.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test public void setBackupActive_nonSystemUser_disabledForSystemUser_ignored() { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(mService.isBackupServiceActive(NON_USER_SYSTEM)); } @Test public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() { int otherUser = NON_USER_SYSTEM + 1; File activateFile = new File(mTestDir, "activate-" + otherUser); - TrampolineTestable.sActivatedFiles.append(otherUser, activateFile); - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + BackupManagerServiceTestable.sActivatedFiles.append(otherUser, activateFile); + mService.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); - assertFalse(mTrampoline.isBackupServiceActive(otherUser)); + assertTrue(mService.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(mService.isBackupServiceActive(otherUser)); activateFile.delete(); } @Test public void setBackupServiceActive_forNonSystemUser_remembersActivated() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); assertTrue(RandomAccessFileUtils.readBoolean( - TrampolineTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), false)); + BackupManagerServiceTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), false)); } @Test public void setBackupServiceActiveFalse_forNonSystemUser_remembersActivated() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, false); + mService.setBackupServiceActive(NON_USER_SYSTEM, false); assertFalse(RandomAccessFileUtils.readBoolean( - TrampolineTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true)); + BackupManagerServiceTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true)); } @Test public void setBackupServiceActiveTwice_forNonSystemUser_remembersLastActivated() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, false); + mService.setBackupServiceActive(NON_USER_SYSTEM, true); + mService.setBackupServiceActive(NON_USER_SYSTEM, false); assertFalse(RandomAccessFileUtils.readBoolean( - TrampolineTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true)); + BackupManagerServiceTestable.sRememberActivatedFiles.get(NON_USER_SYSTEM), true)); } @Test @@ -497,7 +500,7 @@ public class TrampolineTest { } }; - mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener); + mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener); assertEquals(BackupManager.ERROR_BACKUP_NOT_ALLOWED, (int) future.get(5, TimeUnit.SECONDS)); } @@ -505,14 +508,14 @@ public class TrampolineTest { @Test public void selectBackupTransportAsyncForUser_beforeUserUnlockedWithNullListener_doesNotThrow() throws Exception { - mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null); + mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null); // No crash. } @Test public void - selectBackupTransportAsyncForUser_beforeUserUnlockedWithThrowingListener_doesNotThrow() + selectBackupTransportAsyncForUser_beforeUserUnlockedListenerThrowing_doesNotThrow() throws Exception { ISelectBackupTransportCallback.Stub listener = new ISelectBackupTransportCallback.Stub() { @@ -524,7 +527,7 @@ public class TrampolineTest { } }; - mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener); + mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener); // No crash. } @@ -535,44 +538,45 @@ public class TrampolineTest { android.Manifest.permission.DUMP)).thenReturn( PackageManager.PERMISSION_DENIED); - mTrampoline.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]); + mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]); - verifyNoMoreInteractions(mBackupManagerServiceMock); + verifyNoMoreInteractions(mUserBackupManagerService); } public void testGetUserForAncestralSerialNumber() { - TrampolineTestable.sBackupDisabled = false; - Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices); + BackupManagerServiceTestable.sBackupDisabled = false; + BackupManagerService backupManagerService = + new BackupManagerServiceTestable(mContextMock, mUserServices); when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L); - UserHandle user = trampoline.getUserForAncestralSerialNumber(11L); + UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L); assertThat(user).isEqualTo(UserHandle.of(1)); } public void testGetUserForAncestralSerialNumber_whenDisabled() { - TrampolineTestable.sBackupDisabled = true; - Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices); + BackupManagerServiceTestable.sBackupDisabled = true; + BackupManagerService backupManagerService = + new BackupManagerServiceTestable(mContextMock, mUserServices); when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L); - UserHandle user = trampoline.getUserForAncestralSerialNumber(11L); + UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L); assertThat(user).isNull(); } - private static class TrampolineTestable extends Trampoline { + private static class BackupManagerServiceTestable extends BackupManagerService { static boolean sBackupDisabled = false; static int sCallingUserId = -1; static int sCallingUid = -1; - static BackupManagerService sBackupManagerServiceMock = null; static File sSuppressFile = null; static SparseArray<File> sActivatedFiles = new SparseArray<>(); static SparseArray<File> sRememberActivatedFiles = new SparseArray<>(); static UserManager sUserManagerMock = null; - TrampolineTestable(Context context, SparseArray<UserBackupManagerService> userServices) { + BackupManagerServiceTestable( + Context context, SparseArray<UserBackupManagerService> userServices) { super(context, userServices); - mService = sBackupManagerServiceMock; } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java new file mode 100644 index 000000000000..ccf3a908364a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -0,0 +1,748 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.TestCase.assertNotNull; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.app.IActivityManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.IBiometricService; +import android.hardware.biometrics.IBiometricServiceReceiver; +import android.hardware.biometrics.IBiometricServiceReceiverInternal; +import android.hardware.face.FaceManager; +import android.hardware.face.IFaceService; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintService; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.security.KeyStore; + +import com.android.internal.R; +import com.android.internal.statusbar.IStatusBarService; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +@SmallTest +public class BiometricServiceTest { + + private static final String TAG = "BiometricServiceTest"; + + private static final String TEST_PACKAGE_NAME = "test_package"; + + private static final String ERROR_HW_UNAVAILABLE = "hw_unavailable"; + private static final String ERROR_NOT_RECOGNIZED = "not_recognized"; + private static final String ERROR_TIMEOUT = "error_timeout"; + private static final String ERROR_CANCELED = "error_canceled"; + private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process"; + private static final String ERROR_USER_CANCELED = "error_user_canceled"; + + private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty"; + + private BiometricService mBiometricService; + + @Mock + private Context mContext; + @Mock + private ContentResolver mContentResolver; + @Mock + private Resources mResources; + @Mock + private PackageManager mPackageManager; + @Mock + private AppOpsManager mAppOpsManager; + @Mock + IBiometricServiceReceiver mReceiver1; + @Mock + IBiometricServiceReceiver mReceiver2; + @Mock + FingerprintManager mFingerprintManager; + @Mock + FaceManager mFaceManager; + + private static class MockInjector extends BiometricService.Injector { + @Override + IActivityManager getActivityManagerService() { + return mock(IActivityManager.class); + } + + @Override + IStatusBarService getStatusBarService() { + return mock(IStatusBarService.class); + } + + @Override + IFingerprintService getFingerprintService() { + return mock(IFingerprintService.class); + } + + @Override + IFaceService getFaceService() { + return mock(IFaceService.class); + } + + @Override + BiometricService.SettingObserver getSettingObserver(Context context, Handler handler, + List<BiometricService.EnabledOnKeyguardCallback> callbacks) { + return mock(BiometricService.SettingObserver.class); + } + + @Override + KeyStore getKeyStore() { + return mock(KeyStore.class); + } + + @Override + boolean isDebugEnabled(Context context, int userId) { + return false; + } + + @Override + void publishBinderService(BiometricService service, IBiometricService.Stub impl) { + // no-op for test + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); + when(mContext.getSystemService(Context.FINGERPRINT_SERVICE)) + .thenReturn(mFingerprintManager); + when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + when(mContext.getResources()).thenReturn(mResources); + + when(mResources.getString(R.string.biometric_error_hw_unavailable)) + .thenReturn(ERROR_HW_UNAVAILABLE); + when(mResources.getString(R.string.biometric_not_recognized)) + .thenReturn(ERROR_NOT_RECOGNIZED); + when(mResources.getString(R.string.biometric_error_user_canceled)) + .thenReturn(ERROR_USER_CANCELED); + } + + @Test + public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws Exception { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) + .thenReturn(false); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(false); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false); + + mBiometricService = new BiometricService(mContext, new MockInjector()); + mBiometricService.onStart(); + + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + waitForIdle(); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT), eq(ERROR_HW_UNAVAILABLE)); + } + + @Test + public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + + mBiometricService = new BiometricService(mContext, new MockInjector()); + mBiometricService.onStart(); + + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + waitForIdle(); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), any()); + } + + @Test + public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws Exception { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true); + when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + + mBiometricService = new BiometricService(mContext, new MockInjector()); + mBiometricService.onStart(); + + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + waitForIdle(); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE)); + } + + @Test + public void testAuthenticateFace_respectsUserSetting() + throws Exception { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + + mBiometricService = new BiometricService(mContext, new MockInjector()); + mBiometricService.onStart(); + + // Disabled in user settings receives onError + when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + waitForIdle(); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE)); + + // Enrolled, not disabled in settings, user requires confirmation in settings + resetReceiver(); + when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true); + when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt())) + .thenReturn(true); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + waitForIdle(); + verify(mReceiver1, never()).onError(anyInt(), any(String.class)); + verify(mBiometricService.mFaceService).prepareForAuthentication( + eq(true) /* requireConfirmation */, + any(IBinder.class), + anyLong() /* sessionId */, + anyInt() /* userId */, + any(IBiometricServiceReceiverInternal.class), + anyString() /* opPackageName */, + anyInt() /* cookie */, + anyInt() /* callingUid */, + anyInt() /* callingPid */, + anyInt() /* callingUserId */); + + // Enrolled, not disabled in settings, user doesn't require confirmation in settings + resetReceiver(); + when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt())) + .thenReturn(false); + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + waitForIdle(); + verify(mBiometricService.mFaceService).prepareForAuthentication( + eq(false) /* requireConfirmation */, + any(IBinder.class), + anyLong() /* sessionId */, + anyInt() /* userId */, + any(IBiometricServiceReceiverInternal.class), + anyString() /* opPackageName */, + anyInt() /* cookie */, + anyInt() /* callingUid */, + anyInt() /* callingPid */, + anyInt() /* callingUserId */); + } + + @Test + public void testAuthenticate_happyPathWithoutConfirmation() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + mBiometricService = new BiometricService(mContext, new MockInjector()); + mBiometricService.onStart(); + + // Start testing the happy path + invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */); + waitForIdle(); + + // Creates a pending auth session with the correct initial states + assertEquals(mBiometricService.mPendingAuthSession.mState, + BiometricService.STATE_AUTH_CALLED); + + // Invokes <Modality>Service#prepareForAuthentication + ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mReceiver1, never()).onError(anyInt(), any(String.class)); + verify(mBiometricService.mFingerprintService).prepareForAuthentication( + any(IBinder.class), + anyLong() /* sessionId */, + anyInt() /* userId */, + any(IBiometricServiceReceiverInternal.class), + anyString() /* opPackageName */, + cookieCaptor.capture() /* cookie */, + anyInt() /* callingUid */, + anyInt() /* callingPid */, + anyInt() /* callingUserId */); + + // onReadyForAuthentication, mCurrentAuthSession state OK + mBiometricService.mImpl.onReadyForAuthentication(cookieCaptor.getValue(), + anyBoolean() /* requireConfirmation */, anyInt() /* userId */); + waitForIdle(); + assertNull(mBiometricService.mPendingAuthSession); + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_STARTED); + + // startPreparedClient invoked + verify(mBiometricService.mFingerprintService) + .startPreparedClient(cookieCaptor.getValue()); + + // StatusBar showBiometricDialog invoked + verify(mBiometricService.mStatusBarService).showBiometricDialog( + eq(mBiometricService.mCurrentAuthSession.mBundle), + any(IBiometricServiceReceiverInternal.class), + eq(BiometricAuthenticator.TYPE_FINGERPRINT), + anyBoolean() /* requireConfirmation */, + anyInt() /* userId */, + eq(TEST_PACKAGE_NAME)); + + // Hardware authenticated + mBiometricService.mInternalReceiver.onAuthenticationSucceeded( + false /* requireConfirmation */, + new byte[69] /* HAT */); + waitForIdle(); + // Waiting for SystemUI to send dismissed callback + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTHENTICATED_PENDING_SYSUI); + // Notify SystemUI hardware authenticated + verify(mBiometricService.mStatusBarService).onBiometricAuthenticated( + eq(true) /* authenticated */, eq(null) /* failureReason */); + + // SystemUI sends callback with dismissed reason + mBiometricService.mInternalReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED); + waitForIdle(); + // HAT sent to keystore + verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); + // Send onAuthenticated to client + verify(mReceiver1).onAuthenticationSucceeded(); + // Current session becomes null + assertNull(mBiometricService.mCurrentAuthSession); + } + + @Test + public void testAuthenticate_happyPathWithConfirmation() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */); + + // Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not + // sent to KeyStore yet + mBiometricService.mInternalReceiver.onAuthenticationSucceeded( + true /* requireConfirmation */, + new byte[69] /* HAT */); + waitForIdle(); + // Waiting for SystemUI to send confirmation callback + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_PENDING_CONFIRM); + verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class)); + + // SystemUI sends confirm, HAT is sent to keystore and client is notified. + mBiometricService.mInternalReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_CONFIRMED); + waitForIdle(); + verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class)); + verify(mReceiver1).onAuthenticationSucceeded(); + } + + @Test + public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused() + throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onAuthenticationFailed(); + waitForIdle(); + + verify(mBiometricService.mStatusBarService) + .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED)); + verify(mReceiver1).onAuthenticationFailed(); + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_PAUSED); + } + + @Test + public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating() + throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onAuthenticationFailed(); + waitForIdle(); + + verify(mBiometricService.mStatusBarService) + .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED)); + verify(mReceiver1).onAuthenticationFailed(); + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_STARTED); + } + + @Test + public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + // Create a new pending auth session but don't start it yet. HAL contract is that previous + // one must get ERROR_CANCELED. Simulate that here by creating the pending auth session, + // sending ERROR_CANCELED to the current auth session, and then having the second one + // onReadyForAuthentication. + invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */); + waitForIdle(); + + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_STARTED); + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_CANCELED, ERROR_CANCELED); + waitForIdle(); + + // Auth session doesn't become null until SystemUI responds that the animation is completed + assertNotNull(mBiometricService.mCurrentAuthSession); + // ERROR_CANCELED is not sent until SystemUI responded that animation is completed + verify(mReceiver1, never()).onError( + anyInt(), anyString()); + verify(mReceiver2, never()).onError(anyInt(), any(String.class)); + + // SystemUI dialog closed + verify(mBiometricService.mStatusBarService).hideBiometricDialog(); + + // After SystemUI notifies that the animation has completed + mBiometricService.mInternalReceiver + .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED); + waitForIdle(); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), + eq(ERROR_CANCELED)); + assertNull(mBiometricService.mCurrentAuthSession); + } + + @Test + public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, + ERROR_TIMEOUT); + waitForIdle(); + + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_PAUSED); + verify(mBiometricService.mStatusBarService) + .onBiometricAuthenticated(eq(false), eq(ERROR_TIMEOUT)); + // Timeout does not count as fail as per BiometricPrompt documentation. + verify(mReceiver1, never()).onAuthenticationFailed(); + + // No pending auth session. Pressing try again will create one. + assertNull(mBiometricService.mPendingAuthSession); + + // Pressing "Try again" on SystemUI starts a new auth session. + mBiometricService.mInternalReceiver.onTryAgainPressed(); + waitForIdle(); + + // The last one is still paused, and a new one has been created. + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_PAUSED); + assertEquals(mBiometricService.mPendingAuthSession.mState, + BiometricService.STATE_AUTH_CALLED); + + // Test resuming when hardware becomes ready. SystemUI should not be requested to + // show another dialog since it's already showing. + resetStatusBar(); + startPendingAuthSession(mBiometricService); + waitForIdle(); + verify(mBiometricService.mStatusBarService, never()).showBiometricDialog( + any(Bundle.class), + any(IBiometricServiceReceiverInternal.class), + anyInt(), + anyBoolean() /* requireConfirmation */, + anyInt() /* userId */, + anyString()); + } + + @Test + public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireCOnfirmation */); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, + ERROR_TIMEOUT); + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_CANCELED, + ERROR_CANCELED); + waitForIdle(); + + // Client receives error immediately + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), + eq(ERROR_CANCELED)); + // Dialog is hidden immediately + verify(mBiometricService.mStatusBarService).hideBiometricDialog(); + // Auth session is over + assertNull(mBiometricService.mCurrentAuthSession); + } + + @Test + public void testErrorFromHal_whileAuthenticating_waitsForSysUIBeforeNotifyingClient() + throws Exception { + // For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI + // until SystemUI notifies us that the dialog is dismissed at which point the current + // session is done. + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS, + ERROR_UNABLE_TO_PROCESS); + waitForIdle(); + + // Sends error to SystemUI and does not notify client yet + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_ERROR_PENDING_SYSUI); + verify(mBiometricService.mStatusBarService) + .onBiometricError(eq(ERROR_UNABLE_TO_PROCESS)); + verify(mBiometricService.mStatusBarService, never()).hideBiometricDialog(); + verify(mReceiver1, never()).onError(anyInt(), anyString()); + + // SystemUI animation completed, client is notified, auth session is over + mBiometricService.mInternalReceiver + .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR); + waitForIdle(); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS), + eq(ERROR_UNABLE_TO_PROCESS)); + assertNull(mBiometricService.mCurrentAuthSession); + } + + @Test + public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication() + throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver + .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + waitForIdle(); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED), + eq(ERROR_USER_CANCELED)); + verify(mBiometricService.mFingerprintService).cancelAuthenticationFromService( + any(), + any(), + anyInt(), + anyInt(), + anyInt(), + eq(false) /* fromClient */); + assertNull(mBiometricService.mCurrentAuthSession); + } + + @Test + public void testDismissedReasonNegative_whilePaused_doesntInvokeHalCancel() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, + ERROR_TIMEOUT); + mBiometricService.mInternalReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_NEGATIVE); + waitForIdle(); + + verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService( + any(), + any(), + anyInt(), + anyInt(), + anyInt(), + anyBoolean()); + } + + @Test + public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onError( + getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, + ERROR_TIMEOUT); + mBiometricService.mInternalReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + waitForIdle(); + + verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService( + any(), + any(), + anyInt(), + anyInt(), + anyInt(), + anyBoolean()); + } + + @Test + public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FACE); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onAuthenticationSucceeded( + true /* requireConfirmation */, + new byte[69] /* HAT */); + mBiometricService.mInternalReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_USER_CANCEL); + waitForIdle(); + + // doesn't send cancel to HAL + verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService( + any(), + any(), + anyInt(), + anyInt(), + anyInt(), + anyBoolean()); + verify(mReceiver1).onError( + eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED), + eq(ERROR_USER_CANCELED)); + assertNull(mBiometricService.mCurrentAuthSession); + } + + @Test + public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */); + + mBiometricService.mInternalReceiver.onAcquired( + FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY, + FINGERPRINT_ACQUIRED_SENSOR_DIRTY); + waitForIdle(); + + // Sends to SysUI and stays in authenticating state + verify(mBiometricService.mStatusBarService) + .onBiometricHelp(eq(FINGERPRINT_ACQUIRED_SENSOR_DIRTY)); + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_STARTED); + } + + // Helper methods + + private void setupAuthForOnly(int modality) { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) + .thenReturn(false); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false); + + if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) + .thenReturn(true); + when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + } else if (modality == BiometricAuthenticator.TYPE_FACE) { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + } else { + fail("Unknown modality: " + modality); + } + + mBiometricService = new BiometricService(mContext, new MockInjector()); + mBiometricService.onStart(); + + when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true); + } + + private void resetReceiver() { + mReceiver1 = mock(IBiometricServiceReceiver.class); + mReceiver2 = mock(IBiometricServiceReceiver.class); + } + + private void resetStatusBar() { + mBiometricService.mStatusBarService = mock(IStatusBarService.class); + } + + private void invokeAuthenticateAndStart(IBiometricService.Stub service, + IBiometricServiceReceiver receiver, boolean requireConfirmation) throws Exception { + // Request auth, creates a pending session + invokeAuthenticate(service, receiver, requireConfirmation); + waitForIdle(); + + startPendingAuthSession(mBiometricService); + waitForIdle(); + } + + private static void startPendingAuthSession(BiometricService service) throws Exception { + // Get the cookie so we can pretend the hardware is ready to authenticate + // Currently we only support single modality per auth + assertEquals(service.mPendingAuthSession.mModalitiesWaiting.values().size(), 1); + final int cookie = service.mPendingAuthSession.mModalitiesWaiting.values() + .iterator().next(); + assertNotEquals(cookie, 0); + + service.mImpl.onReadyForAuthentication(cookie, + anyBoolean() /* requireConfirmation */, anyInt() /* userId */); + } + + private static void invokeAuthenticate(IBiometricService.Stub service, + IBiometricServiceReceiver receiver, boolean requireConfirmation) throws Exception { + service.authenticate( + new Binder() /* token */, + 0 /* sessionId */, + 0 /* userId */, + receiver, + TEST_PACKAGE_NAME /* packageName */, + createTestBiometricPromptBundle(requireConfirmation), + null /* IBiometricConfirmDeviceCredentialCallback */); + } + + private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation) { + final Bundle bundle = new Bundle(); + bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation); + return bundle; + } + + private static int getCookieForCurrentSession(BiometricService.AuthSession session) { + assertEquals(session.mModalitiesMatched.values().size(), 1); + return session.mModalitiesMatched.values().iterator().next(); + } + + private static void waitForIdle() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index c1c0a308e48a..57caa1db423c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -2766,6 +2766,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testReadPolicyXml_readSnoozedNotificationsFromXml() throws Exception { + final String upgradeXml = "<notification-policy version=\"1\">" + + "<snoozed-notifications>></snoozed-notifications>" + + "</notification-policy>"; + mService.readPolicyXml( + new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())), + false, + UserHandle.USER_ALL); + verify(mSnoozeHelper, times(1)).readXml(any(XmlPullParser.class)); + } + + @Test public void testReadPolicyXml_readApprovedServicesFromSettings() throws Exception { final String preupgradeXml = "<notification-policy version=\"1\">" + "<ranking></ranking>" diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 2e7277f5af01..36175a93f667 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; @@ -37,9 +38,11 @@ import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.util.IntArray; +import android.util.Xml; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.FastXmlSerializer; import com.android.server.UiServiceTestCase; import org.junit.Before; @@ -48,6 +51,15 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; @SmallTest @RunWith(AndroidJUnit4.class) @@ -69,6 +81,117 @@ public class SnoozeHelperTest extends UiServiceTestCase { } @Test + public void testWriteXMLformattedCorrectly_testReadingCorrectTime() + throws XmlPullParserException, IOException { + final String max_time_str = Long.toString(Long.MAX_VALUE); + final String xml_string = "<snoozed-notifications>" + + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" " + + "pkg=\"pkg\" key=\"key\" time=\"" + max_time_str + "\"/>" + + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" " + + "pkg=\"pkg\" key=\"key2\" time=\"" + max_time_str + "\"/>" + + "</snoozed-notifications>"; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml_string.getBytes())), null); + mSnoozeHelper.readXml(parser); + assertTrue("Should read the notification time from xml and it should be more than zero", + 0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + 0, "pkg", "key").doubleValue()); + } + + @Test + public void testWriteXMLformattedCorrectly_testCorrectContextURI() + throws XmlPullParserException, IOException { + final String max_time_str = Long.toString(Long.MAX_VALUE); + final String xml_string = "<snoozed-notifications>" + + "<context version=\"1\" user-id=\"0\" notification=\"notification\" " + + "pkg=\"pkg\" key=\"key\" id=\"uri\"/>" + + "<context version=\"1\" user-id=\"0\" notification=\"notification\" " + + "pkg=\"pkg\" key=\"key2\" id=\"uri\"/>" + + "</snoozed-notifications>"; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml_string.getBytes())), null); + mSnoozeHelper.readXml(parser); + assertEquals("Should read the notification context from xml and it should be `uri", + "uri", mSnoozeHelper.getSnoozeContextForUnpostedNotification( + 0, "pkg", "key")); + } + + @Test + public void testReadValidSnoozedFromCorrectly_timeDeadline() + throws XmlPullParserException, IOException { + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + mSnoozeHelper.snooze(r, 999999999); + XmlSerializer serializer = new FastXmlSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + mSnoozeHelper.writeXml(serializer); + serializer.endDocument(); + serializer.flush(); + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), "utf-8"); + mSnoozeHelper.readXml(parser); + assertTrue("Should read the notification time from xml and it should be more than zero", + 0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + 0, "pkg", r.getKey()).doubleValue()); + } + + + @Test + public void testReadExpiredSnoozedNotification() throws + XmlPullParserException, IOException, InterruptedException { + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + mSnoozeHelper.snooze(r, 0); + // Thread.sleep(100); + XmlSerializer serializer = new FastXmlSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + mSnoozeHelper.writeXml(serializer); + serializer.endDocument(); + serializer.flush(); + Thread.sleep(10); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), "utf-8"); + mSnoozeHelper.readXml(parser); + int systemUser = UserHandle.SYSTEM.getIdentifier(); + assertTrue("Should see a past time returned", + System.currentTimeMillis() > mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + systemUser, "pkg", r.getKey()).longValue()); + } + + @Test + public void testCleanupContextShouldRemovePersistedRecord() { + NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); + mSnoozeHelper.snooze(r, "context"); + mSnoozeHelper.cleanupPersistedContext(r.sbn.getKey()); + assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification( + r.getUser().getIdentifier(), + r.sbn.getPackageName(), + r.sbn.getKey() + )); + } + + @Test + public void testReadNoneSnoozedNotification() throws XmlPullParserException, + IOException, InterruptedException { + NotificationRecord r = getNotificationRecord( + "pkg", 1, "one", UserHandle.SYSTEM); + mSnoozeHelper.snooze(r, 0); + + assertEquals("should see a zero value for unsnoozed notification", + 0L, + mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + UserHandle.SYSTEM.getIdentifier(), + "not_my_package", r.getKey()).longValue()); + } + + @Test public void testSnoozeForTime() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); mSnoozeHelper.snooze(r, 1000); @@ -84,7 +207,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { @Test public void testSnooze() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); - mSnoozeHelper.snooze(r); + mSnoozeHelper.snooze(r, (String) null); verify(mAm, never()).setExactAndAllowWhileIdle( anyInt(), anyLong(), any(PendingIntent.class)); assertTrue(mSnoozeHelper.isSnoozed( diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java index 63b9198bda90..6c78f6f0443f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java @@ -28,6 +28,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; @@ -140,7 +141,8 @@ public class ActivityDisplayTests extends ActivityTestsBase { public void testNotResumeHomeStackOnRemovingDisplay() { // Create a display which supports system decoration and allows reparenting stacks to // another display when the display is removed. - final ActivityDisplay display = spy(createNewActivityDisplay()); + final ActivityDisplay display = createNewActivityDisplay(); + spyOn(display); doReturn(false).when(display).shouldDestroyContentOnRemove(); doReturn(true).when(display).supportsSystemDecorations(); mRootActivityContainer.addChild(display, ActivityDisplay.POSITION_TOP); @@ -304,14 +306,18 @@ public class ActivityDisplayTests extends ActivityTestsBase { ACTIVITY_TYPE_STANDARD, ON_TOP); final ActivityStack stack4 = display.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final TaskRecord task1 = new TaskBuilder(mService.mStackSupervisor).setStack( - stack1).setTaskId(1).build(); - final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor).setStack( - stack2).setTaskId(2).build(); - final TaskRecord task3 = new TaskBuilder(mService.mStackSupervisor).setStack( - stack3).setTaskId(3).build(); - final TaskRecord task4 = new TaskBuilder(mService.mStackSupervisor).setStack( - stack4).setTaskId(4).build(); + final TaskRecord task1 = new TaskBuilder(mService.mStackSupervisor) + .setStack(stack1) + .build(); + final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor) + .setStack(stack2) + .build(); + final TaskRecord task3 = new TaskBuilder(mService.mStackSupervisor) + .setStack(stack3) + .build(); + final TaskRecord task4 = new TaskBuilder(mService.mStackSupervisor) + .setStack(stack4) + .build(); // Reordering stacks while removing stacks. doAnswer(invocation -> { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 23bae8881aa1..977dd8e5951e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -61,13 +61,13 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { private ActivityMetricsLaunchObserver mLaunchObserver; private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry; - private TestActivityStack mStack; + private ActivityStack mStack; private TaskRecord mTask; private ActivityRecord mActivityRecord; private ActivityRecord mActivityRecordTrampoline; @Before - public void setUpAMLO() throws Exception { + public void setUpAMLO() { mLaunchObserver = mock(ActivityMetricsLaunchObserver.class); // ActivityStackSupervisor always creates its own instance of ActivityMetricsLogger. @@ -78,15 +78,19 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful. // This seems to be the easiest way to create an ActivityRecord. - mStack = mRootActivityContainer.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); - mActivityRecord = new ActivityBuilder(mService).setTask(mTask).build(); + mStack = new StackBuilder(mRootActivityContainer) + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setOnTop(true) + .setCreateActivity(true) + .build(); + mTask = mStack.topTask(); + mActivityRecord = mTask.getTopActivity(); mActivityRecordTrampoline = new ActivityBuilder(mService).setTask(mTask).build(); } @After - public void tearDownAMLO() throws Exception { + public void tearDownAMLO() { if (mLaunchObserverRegistry != null) { // Don't NPE if setUp failed. mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 7b252cbfc0b0..642df4311939 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -16,6 +16,12 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; +import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.os.Process.NOBODY_UID; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Surface.ROTATION_0; @@ -29,6 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; @@ -71,6 +78,7 @@ import android.os.PersistableBundle; import android.platform.test.annotations.Presubmit; import android.util.MergedConfiguration; import android.util.MutableBoolean; +import android.view.DisplayInfo; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner.Stub; import android.view.RemoteAnimationAdapter; @@ -79,7 +87,6 @@ import android.view.RemoteAnimationTarget; import androidx.test.filters.MediumTest; import com.android.internal.R; -import com.android.server.wm.utils.WmDisplayCutout; import org.junit.Before; import org.junit.Test; @@ -96,13 +103,13 @@ import java.util.concurrent.TimeUnit; @MediumTest @Presubmit public class ActivityRecordTests extends ActivityTestsBase { - private TestActivityStack mStack; + private ActivityStack mStack; private TaskRecord mTask; private ActivityRecord mActivity; @Before public void setUp() throws Exception { - mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build(); + mStack = new StackBuilder(mRootActivityContainer).build(); mTask = mStack.getChildAt(0); mActivity = mTask.getTopActivity(); @@ -113,13 +120,13 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testStackCleanupOnClearingTask() { mActivity.setTask(null); - assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); + verify(mStack, times(1)).onActivityRemovedFromStack(any()); } @Test public void testStackCleanupOnActivityRemoval() { mTask.removeActivity(mActivity); - assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); + verify(mStack, times(1)).onActivityRemovedFromStack(any()); } @Test @@ -134,7 +141,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack) .build(); mActivity.reparent(newTask, 0, null /*reason*/); - assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0); + verify(mStack, times(0)).onActivityRemovedFromStack(any()); } @Test @@ -181,7 +188,7 @@ public class ActivityRecordTests extends ActivityTestsBase { assertTrue(mActivity.isState(STARTED)); mStack.mTranslucentActivityWaiting = null; - topActivity.changeWindowTranslucency(false); + topActivity.setOccludesParent(false); mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind non-opaque"); mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); assertTrue(mActivity.isState(STARTED)); @@ -261,8 +268,8 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testNewParentConfigurationIncrementsSeq() { final Configuration newConfig = new Configuration( mTask.getRequestedOverrideConfiguration()); - newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT - ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT; + newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT + ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; final int prevSeq = mActivity.getMergedOverrideConfiguration().seq; mTask.onRequestedOverrideConfigurationChanged(newConfig); @@ -277,7 +284,7 @@ public class ActivityRecordTests extends ActivityTestsBase { .getRequestedOverrideConfiguration(); final Configuration newConfig = new Configuration(); - newConfig.orientation = Configuration.ORIENTATION_PORTRAIT; + newConfig.orientation = ORIENTATION_PORTRAIT; final int prevSeq = mActivity.getMergedOverrideConfiguration().seq; mActivity.onRequestedOverrideConfigurationChanged(newConfig); @@ -293,10 +300,11 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), mActivity.getConfiguration())); - mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION; + mActivity.info.configChanges &= ~CONFIG_ORIENTATION; final Configuration newConfig = new Configuration(mTask.getConfiguration()); - newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT - ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT; + newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT + ? ORIENTATION_LANDSCAPE + : ORIENTATION_PORTRAIT; mTask.onRequestedOverrideConfigurationChanged(newConfig); mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE; @@ -315,13 +323,14 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), mActivity.getConfiguration())); - mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION; + mActivity.info.configChanges &= ~CONFIG_ORIENTATION; final Configuration newConfig = new Configuration(mTask.getConfiguration()); - newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT - ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT; + newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT + ? ORIENTATION_LANDSCAPE + : ORIENTATION_PORTRAIT; mTask.onRequestedOverrideConfigurationChanged(newConfig); - doReturn(true).when(mTask.getTask()).isDragResizing(); + doReturn(true).when(mTask.mTask).isDragResizing(); mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE; @@ -355,30 +364,39 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testSetRequestedOrientationUpdatesConfiguration() throws Exception { + mActivity = new ActivityBuilder(mService) + .setTask(mTask) + .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT) + .build(); mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing"); - mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration()); mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), mActivity.getConfiguration())); - mActivity.info.configChanges |= ActivityInfo.CONFIG_ORIENTATION; final Configuration newConfig = new Configuration(mActivity.getConfiguration()); - newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT - ? Configuration.ORIENTATION_LANDSCAPE - : Configuration.ORIENTATION_PORTRAIT; + final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); + final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp); + if (newConfig.orientation == ORIENTATION_PORTRAIT) { + newConfig.orientation = ORIENTATION_LANDSCAPE; + newConfig.screenWidthDp = longSide; + newConfig.screenHeightDp = shortSide; + } else { + newConfig.orientation = ORIENTATION_PORTRAIT; + newConfig.screenWidthDp = shortSide; + newConfig.screenHeightDp = longSide; + } // Mimic the behavior that display doesn't handle app's requested orientation. - doAnswer(invocation -> { - mTask.onConfigurationChanged(newConfig); - return null; - }).when(mActivity.mAppWindowToken).setOrientation(anyInt(), any(), any()); + final DisplayContent dc = mTask.mTask.getDisplayContent(); + doReturn(false).when(dc).onDescendantOrientationChanged(any(), any()); + doReturn(false).when(dc).handlesOrientationChangeFromDescendant(); final int requestedOrientation; switch (newConfig.orientation) { - case Configuration.ORIENTATION_LANDSCAPE: - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + case ORIENTATION_LANDSCAPE: + requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE; break; - case Configuration.ORIENTATION_PORTRAIT: + case ORIENTATION_PORTRAIT: requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; break; default: @@ -421,24 +439,33 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testPushConfigurationWhenLaunchTaskBehind() throws Exception { + mActivity = new ActivityBuilder(mService) + .setTask(mTask) + .setLaunchTaskBehind(true) + .setConfigChanges(CONFIG_ORIENTATION) + .build(); mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing"); - final TestActivityStack stack = (TestActivityStack) new StackBuilder(mRootActivityContainer) - .build(); + final ActivityStack stack = new StackBuilder(mRootActivityContainer).build(); try { - stack.setIsTranslucent(false); + doReturn(false).when(stack).isStackTranslucent(any()); assertFalse(mStack.shouldBeVisible(null /* starting */)); - mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration()); mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), mActivity.getConfiguration())); - mActivity.mLaunchTaskBehind = true; - mActivity.info.configChanges |= ActivityInfo.CONFIG_ORIENTATION; final Configuration newConfig = new Configuration(mActivity.getConfiguration()); - newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT - ? Configuration.ORIENTATION_LANDSCAPE - : Configuration.ORIENTATION_PORTRAIT; + final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); + final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp); + if (newConfig.orientation == ORIENTATION_PORTRAIT) { + newConfig.orientation = ORIENTATION_LANDSCAPE; + newConfig.screenWidthDp = longSide; + newConfig.screenHeightDp = shortSide; + } else { + newConfig.orientation = ORIENTATION_PORTRAIT; + newConfig.screenWidthDp = shortSide; + newConfig.screenHeightDp = longSide; + } mTask.onConfigurationChanged(newConfig); @@ -457,7 +484,7 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testShouldPauseWhenMakeClientVisible() { ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - topActivity.changeWindowTranslucency(false); + topActivity.setOccludesParent(false); mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing"); mActivity.makeClientVisible(); assertEquals(STARTED, mActivity.getState()); @@ -468,6 +495,7 @@ public class ActivityRecordTests extends ActivityTestsBase { setupDisplayContentForCompatDisplayInsets(); final int decorHeight = 200; // e.g. The device has cutout. final DisplayPolicy policy = setupDisplayAndParentSize(600, 800).getDisplayPolicy(); + spyOn(policy); doAnswer(invocationOnMock -> { final int rotation = invocationOnMock.<Integer>getArgument(0); final Rect insets = invocationOnMock.<Rect>getArgument(4); @@ -482,7 +510,7 @@ public class ActivityRecordTests extends ActivityTestsBase { doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) .when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility(); - mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1; ensureActivityConfiguration(); // The parent configuration doesn't change since the first resolved configuration, so the @@ -506,19 +534,32 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testSizeCompatMode_FixedScreenConfigurationWhenMovingToDisplay() { // Initialize different bounds on a new display. - final ActivityDisplay newDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP); - newDisplay.getWindowConfiguration().setAppBounds(new Rect(0, 0, 1000, 2000)); - newDisplay.getConfiguration().densityDpi = 300; - - mTask.getConfiguration().densityDpi = 200; - prepareFixedAspectRatioUnresizableActivity(); + final Rect newDisplayBounds = new Rect(0, 0, 1000, 2000); + DisplayInfo info = new DisplayInfo(); + mService.mContext.getDisplay().getDisplayInfo(info); + info.logicalWidth = newDisplayBounds.width(); + info.logicalHeight = newDisplayBounds.height(); + info.logicalDensityDpi = 300; + + final ActivityDisplay newDisplay = + addNewActivityDisplayAt(info, ActivityDisplay.POSITION_TOP); + + final Configuration c = + new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration()); + c.densityDpi = 200; + mStack.getDisplay().onRequestedOverrideConfigurationChanged(c); + mActivity = new ActivityBuilder(mService) + .setTask(mTask) + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setMaxAspectRatio(1.5f) + .build(); + mActivity.visible = true; final Rect originalBounds = new Rect(mActivity.getBounds()); final int originalDpi = mActivity.getConfiguration().densityDpi; // Move the non-resizable activity to the new display. mStack.reparent(newDisplay, true /* onTop */, false /* displayRemoved */); - ensureActivityConfiguration(); assertEquals(originalBounds, mActivity.getBounds()); assertEquals(originalDpi, mActivity.getConfiguration().densityDpi); @@ -531,9 +572,9 @@ public class ActivityRecordTests extends ActivityTestsBase { when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn( ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds()); - mTask.getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT; + mTask.getConfiguration().orientation = ORIENTATION_PORTRAIT; mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; ensureActivityConfiguration(); final Rect originalBounds = new Rect(mActivity.getBounds()); @@ -549,7 +590,7 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testSizeCompatMode_FixedScreenLayoutSizeBits() { final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO | Configuration.SCREENLAYOUT_SIZE_NORMAL; - mTask.getConfiguration().screenLayout = fixedScreenLayout + mTask.getRequestedOverrideConfiguration().screenLayout = fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR; prepareFixedAspectRatioUnresizableActivity(); @@ -579,7 +620,7 @@ public class ActivityRecordTests extends ActivityTestsBase { mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 600, 1200)); // Simulate the display changes orientation. - doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION + doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION | ActivityInfo.CONFIG_WINDOW_CONFIGURATION) .when(display).getLastOverrideConfigurationChanges(); mActivity.onConfigurationChanged(mTask.getConfiguration()); @@ -754,29 +795,27 @@ public class ActivityRecordTests extends ActivityTestsBase { /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */ private void prepareFixedAspectRatioUnresizableActivity() { setupDisplayContentForCompatDisplayInsets(); - when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn( - ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; mActivity.info.maxAspectRatio = 1.5f; + mActivity.visible = true; ensureActivityConfiguration(); } private void setupDisplayContentForCompatDisplayInsets() { final Rect displayBounds = mStack.getDisplay().getBounds(); - final DisplayContent displayContent = setupDisplayAndParentSize( - displayBounds.width(), displayBounds.height()); - doReturn(mock(DisplayPolicy.class)).when(displayContent).getDisplayPolicy(); - doReturn(mock(WmDisplayCutout.class)).when(displayContent) - .calculateDisplayCutoutForRotation(anyInt()); + setupDisplayAndParentSize(displayBounds.width(), displayBounds.height()); } private DisplayContent setupDisplayAndParentSize(int width, int height) { - // The DisplayContent is already a mocked object. final DisplayContent displayContent = mStack.getDisplay().mDisplayContent; displayContent.mBaseDisplayWidth = width; displayContent.mBaseDisplayHeight = height; - mTask.getWindowConfiguration().setAppBounds(0, 0, width, height); - mTask.getWindowConfiguration().setRotation(ROTATION_0); + final Configuration c = + new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration()); + c.windowConfiguration.setBounds(new Rect(0, 0, width, height)); + c.windowConfiguration.setAppBounds(0, 0, width, height); + c.windowConfiguration.setRotation(ROTATION_0); + mStack.getDisplay().onRequestedOverrideConfigurationChanged(c); return displayContent; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index e5278d81767e..ff7b1fadbaf9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -29,7 +29,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; @@ -44,6 +44,7 @@ import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; +import static com.android.server.wm.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT; import static com.google.common.truth.Truth.assertThat; @@ -83,8 +84,9 @@ public class ActivityStackTests extends ActivityTestsBase { @Before public void setUp() throws Exception { mDefaultDisplay = mRootActivityContainer.getDefaultDisplay(); - mStack = spy(mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, - true /* onTop */)); + mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + spyOn(mStack); mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); } @@ -140,10 +142,8 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack destStack = mRootActivityContainer.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TaskRecord destTask = new TaskBuilder(mSupervisor).setStack(destStack).build(); - - mTask.removeActivity(r); - destTask.addActivityToTop(r); + mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_STACK_TO_FRONT, false, false, + "testResumedActivityFromActivityReparenting"); assertNull(mStack.getResumedActivity()); assertEquals(r, destStack.getResumedActivity()); @@ -313,45 +313,50 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testShouldBeVisible_Fullscreen() { - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + // Add an activity to the pinned stack so it isn't considered empty for visibility check. + final ActivityRecord pinnedActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .setStack(pinnedStack) + .build(); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack // should be visible since it is always on-top. - fullscreenStack.setIsTranslucent(false); + doReturn(false).when(fullscreenStack).isStackTranslucent(any()); assertFalse(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); assertTrue(fullscreenStack.shouldBeVisible(null /* starting */)); // Home stack should be visible behind a translucent fullscreen stack. - fullscreenStack.setIsTranslucent(true); + doReturn(true).when(fullscreenStack).isStackTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); } @Test public void testShouldBeVisible_SplitScreen() { - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); // Home stack should always be fullscreen for this test. - homeStack.setSupportsSplitScreen(false); - final TestActivityStack splitScreenPrimary = + doReturn(false).when(homeStack).supportsSplitScreenWindowingMode(); + final ActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack splitScreenSecondary = + final ActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Home stack shouldn't be visible if both halves of split-screen are opaque. - splitScreenPrimary.setIsTranslucent(false); - splitScreenSecondary.setIsTranslucent(false); + doReturn(false).when(splitScreenPrimary).isStackTranslucent(any()); + doReturn(false).when(splitScreenSecondary).isStackTranslucent(any()); assertFalse(homeStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -362,7 +367,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary.getVisibility(null /* starting */)); // Home stack should be visible if one of the halves of split-screen is translucent. - splitScreenPrimary.setIsTranslucent(true); + doReturn(true).when(splitScreenPrimary).isStackTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -373,12 +378,12 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(STACK_VISIBILITY_VISIBLE, splitScreenSecondary.getVisibility(null /* starting */)); - final TestActivityStack splitScreenSecondary2 = + final ActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // First split-screen secondary shouldn't be visible behind another opaque split-split // secondary. - splitScreenSecondary2.setIsTranslucent(false); + doReturn(false).when(splitScreenSecondary2).isStackTranslucent(any()); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(STACK_VISIBILITY_INVISIBLE, @@ -388,7 +393,7 @@ public class ActivityStackTests extends ActivityTestsBase { // First split-screen secondary should be visible behind another translucent split-screen // secondary. - splitScreenSecondary2.setIsTranslucent(true); + doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any()); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -396,11 +401,11 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(STACK_VISIBILITY_VISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); - final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. - assistantStack.setIsTranslucent(false); + doReturn(false).when(assistantStack).isStackTranslucent(any()); assertTrue(assistantStack.shouldBeVisible(null /* starting */)); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -415,7 +420,7 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary2.getVisibility(null /* starting */)); // Split-screen stacks should be visible behind a translucent fullscreen stack. - assistantStack.setIsTranslucent(true); + doReturn(true).when(assistantStack).isStackTranslucent(any()); assertTrue(assistantStack.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -430,9 +435,9 @@ public class ActivityStackTests extends ActivityTestsBase { splitScreenSecondary2.getVisibility(null /* starting */)); // Assistant stack shouldn't be visible behind translucent split-screen stack - assistantStack.setIsTranslucent(false); - splitScreenPrimary.setIsTranslucent(true); - splitScreenSecondary2.setIsTranslucent(true); + doReturn(false).when(assistantStack).isStackTranslucent(any()); + doReturn(true).when(splitScreenPrimary).isStackTranslucent(any()); + doReturn(true).when(splitScreenSecondary2).isStackTranslucent(any()); splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen"); splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen"); assertFalse(assistantStack.shouldBeVisible(null /* starting */)); @@ -450,10 +455,10 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testGetVisibility_FullscreenBehindTranslucent() { - final TestActivityStack bottomStack = + final ActivityStack bottomStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final TestActivityStack translucentStack = + final ActivityStack translucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); @@ -465,13 +470,13 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testGetVisibility_FullscreenBehindTranslucentAndOpaque() { - final TestActivityStack bottomStack = + final ActivityStack bottomStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final TestActivityStack translucentStack = + final ActivityStack translucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final TestActivityStack opaqueStack = + final ActivityStack opaqueStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); @@ -483,13 +488,13 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testGetVisibility_FullscreenBehindOpaqueAndTranslucent() { - final TestActivityStack bottomStack = + final ActivityStack bottomStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final TestActivityStack opaqueStack = + final ActivityStack opaqueStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final TestActivityStack translucentStack = + final ActivityStack translucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); @@ -502,10 +507,10 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testGetVisibility_FullscreenTranslucentBehindTranslucent() { - final TestActivityStack bottomTranslucentStack = + final ActivityStack bottomTranslucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final TestActivityStack translucentStack = + final ActivityStack translucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); @@ -517,10 +522,10 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testGetVisibility_FullscreenTranslucentBehindOpaque() { - final TestActivityStack bottomTranslucentStack = + final ActivityStack bottomTranslucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final TestActivityStack opaqueStack = + final ActivityStack opaqueStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); @@ -531,10 +536,10 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testGetVisibility_FullscreenBehindTranslucentAndPip() { - final TestActivityStack bottomStack = + final ActivityStack bottomStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final TestActivityStack translucentStack = + final ActivityStack translucentStack = createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, @@ -544,22 +549,34 @@ public class ActivityStackTests extends ActivityTestsBase { bottomStack.getVisibility(null /* starting */)); assertEquals(STACK_VISIBILITY_VISIBLE, translucentStack.getVisibility(null /* starting */)); + // Add an activity to the pinned stack so it isn't considered empty for visibility check. + final ActivityRecord pinnedActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .setStack(pinnedStack) + .build(); assertEquals(STACK_VISIBILITY_VISIBLE, pinnedStack.getVisibility(null /* starting */)); } @Test public void testShouldBeVisible_Finishing() { - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack translucentStack = createStackForShouldBeVisibleTest( + ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked(); + if (topRunningHomeActivity == null) { + topRunningHomeActivity = new ActivityBuilder(mService) + .setStack(homeStack) + .setCreateTask(true) + .build(); + } + + final ActivityStack translucentStack = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - translucentStack.setIsTranslucent(true); + doReturn(true).when(translucentStack).isStackTranslucent(any()); assertTrue(homeStack.shouldBeVisible(null /* starting */)); assertTrue(translucentStack.shouldBeVisible(null /* starting */)); - final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked(); topRunningHomeActivity.finishing = true; final ActivityRecord topRunningTranslucentActivity = translucentStack.topRunningActivityLocked(); @@ -577,13 +594,13 @@ public class ActivityStackTests extends ActivityTestsBase { public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() { mDefaultDisplay.removeChild(mStack); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - homeStack.setIsTranslucent(false); - fullscreenStack.setIsTranslucent(false); + doReturn(false).when(homeStack).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack).isStackTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -596,13 +613,13 @@ public class ActivityStackTests extends ActivityTestsBase { public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() { mDefaultDisplay.removeChild(mStack); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - homeStack.setIsTranslucent(false); - fullscreenStack.setIsTranslucent(true); + doReturn(false).when(homeStack).isStackTranslucent(any()); + doReturn(true).when(fullscreenStack).isStackTranslucent(any()); // Ensure that we don't move the home stack if it is already behind the top fullscreen stack int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -615,13 +632,13 @@ public class ActivityStackTests extends ActivityTestsBase { public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() { mDefaultDisplay.removeChild(mStack); - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - homeStack.setIsTranslucent(false); - fullscreenStack.setIsTranslucent(false); + doReturn(false).when(homeStack).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack).isStackTranslucent(any()); // Ensure we don't move the home stack if it is already on top int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -634,20 +651,20 @@ public class ActivityStackTests extends ActivityTestsBase { public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { mDefaultDisplay.removeChild(mStack); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - homeStack.setIsTranslucent(false); - fullscreenStack1.setIsTranslucent(false); - fullscreenStack2.setIsTranslucent(false); + doReturn(false).when(homeStack).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack2).isStackTranslucent(any()); // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the // pinned stack @@ -661,18 +678,18 @@ public class ActivityStackTests extends ActivityTestsBase { testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() { mDefaultDisplay.removeChild(mStack); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - homeStack.setIsTranslucent(false); - fullscreenStack1.setIsTranslucent(false); - fullscreenStack2.setIsTranslucent(true); + doReturn(false).when(homeStack).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); + doReturn(true).when(fullscreenStack2).isStackTranslucent(any()); // Ensure that we move the home stack behind the bottom most non-translucent fullscreen // stack @@ -685,18 +702,18 @@ public class ActivityStackTests extends ActivityTestsBase { public void testMoveHomeStackBehindStack_BehindHomeStack() { mDefaultDisplay.removeChild(mStack); - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - homeStack.setIsTranslucent(false); - fullscreenStack1.setIsTranslucent(false); - fullscreenStack2.setIsTranslucent(false); + doReturn(false).when(homeStack).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack1).isStackTranslucent(any()); + doReturn(false).when(fullscreenStack2).isStackTranslucent(any()); // Ensure we don't move the home stack behind itself int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); @@ -708,19 +725,19 @@ public class ActivityStackTests extends ActivityTestsBase { public void testMoveHomeStackBehindStack() { mDefaultDisplay.removeChild(mStack); - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack1); @@ -735,13 +752,13 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testSetAlwaysOnTop() { - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(pinnedStack, mDefaultDisplay.getStackAbove(homeStack)); - final TestActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest( + final ActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); alwaysOnTopStack.setAlwaysOnTop(true); @@ -749,13 +766,13 @@ public class ActivityStackTests extends ActivityTestsBase { // Ensure (non-pinned) always on top stack is put below pinned stack. assertEquals(pinnedStack, mDefaultDisplay.getStackAbove(alwaysOnTopStack)); - final TestActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest( + final ActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Ensure non always on top stack is put below always on top stacks. assertEquals(alwaysOnTopStack, mDefaultDisplay.getStackAbove(nonAlwaysOnTopStack)); - final TestActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest( + final ActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); alwaysOnTopStack2.setAlwaysOnTop(true); @@ -780,18 +797,18 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testSplitScreenMoveToFront() { - final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest( + final ActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest( + final ActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - splitScreenPrimary.setIsTranslucent(false); - splitScreenSecondary.setIsTranslucent(false); - assistantStack.setIsTranslucent(false); + doReturn(false).when(splitScreenPrimary).isStackTranslucent(any()); + doReturn(false).when(splitScreenSecondary).isStackTranslucent(any()); + doReturn(false).when(assistantStack).isStackTranslucent(any()); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); @@ -804,18 +821,18 @@ public class ActivityStackTests extends ActivityTestsBase { assertFalse(assistantStack.shouldBeVisible(null /* starting */)); } - private TestActivityStack createStandardStackForVisibilityTest(int windowingMode, + private ActivityStack createStandardStackForVisibilityTest(int windowingMode, boolean translucent) { - final TestActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay, + final ActivityStack stack = createStackForShouldBeVisibleTest(mDefaultDisplay, windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); - stack.setIsTranslucent(translucent); + doReturn(translucent).when(stack).isStackTranslucent(any()); return stack; } @SuppressWarnings("TypeParameterUnusedInFormals") private <T extends ActivityStack> T createStackForShouldBeVisibleTest( ActivityDisplay display, int windowingMode, int activityType, boolean onTop) { - final T stack; + final ActivityStack stack; if (activityType == ACTIVITY_TYPE_HOME) { // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService stack = mDefaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); @@ -825,11 +842,15 @@ public class ActivityStackTests extends ActivityTestsBase { mDefaultDisplay.positionChildAtBottom(stack); } } else { - stack = display.createStack(windowingMode, activityType, onTop); - final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack) - .setCreateTask(true).build(); + stack = new StackBuilder(mRootActivityContainer) + .setDisplay(display) + .setWindowingMode(windowingMode) + .setActivityType(activityType) + .setOnTop(onTop) + .setCreateActivity(true) + .build(); } - return stack; + return (T) stack; } @Test @@ -961,11 +982,16 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testAdjustFocusedStackToHomeWhenNoActivity() { + final ActivityStack homeStask = mDefaultDisplay.getHomeStack(); + TaskRecord homeTask = homeStask.topTask(); + if (homeTask == null) { + // Create home task if there isn't one. + homeTask = new TaskBuilder(mSupervisor).setStack(homeStask).build(); + } + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); mStack.moveToFront("testAdjustFocusedStack"); - final ActivityStack homeStask = mDefaultDisplay.getHomeStack(); - final TaskRecord homeTask = homeStask.topTask(); // Simulate that home activity has not been started or is force-stopped. homeStask.removeTask(homeTask, "testAdjustFocusedStack", REMOVE_TASK_MODE_DESTROYING); @@ -981,6 +1007,14 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + ActivityRecord activity = homeStack.topRunningActivityLocked(); + if (activity == null) { + activity = new ActivityBuilder(mService) + .setStack(homeStack) + .setCreateTask(true) + .build(); + } + // Home stack should not be destroyed immediately. final ActivityRecord activity1 = finishCurrentActivity(homeStack); assertEquals(FINISHING, activity1.getState()); @@ -1068,7 +1102,7 @@ public class ActivityStackTests extends ActivityTestsBase { public void testStackOrderChangedOnPositionStack() { StackOrderChangedListener listener = new StackOrderChangedListener(); try { - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mDefaultDisplay.registerStackOrderChangedListener(listener); 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 3d944671ef25..81fbfe4e8641 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -79,6 +79,7 @@ import android.view.Gravity; import androidx.test.filters.SmallTest; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; +import com.android.server.wm.utils.MockTracker; import org.junit.Before; import org.junit.Test; @@ -186,6 +187,19 @@ public class ActivityStarterTests extends ActivityTestsBase { verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult); } + private void verifyStartActivityPreconditions(int preconditions, int launchFlags, + int expectedResult) { + // We track mocks created here because this is used in a single test + // (testStartActivityPreconditions) as a specific case, and mocks created inside it won't be + // used for other cases. To avoid extensive memory usage, we clean up all used mocks after + // each case. This is necessary because usually we only clean up mocks after a test + // finishes, but this test creates too many mocks that the intermediate memory usage can be + // ~0.8 GiB and thus very susceptible to OutOfMemoryException. + try (MockTracker tracker = new MockTracker()) { + verifyStartActivityPreconditionsUntracked(preconditions, launchFlags, expectedResult); + } + } + /** * Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller * provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP}) @@ -197,7 +211,7 @@ public class ActivityStarterTests extends ActivityTestsBase { * @param launchFlags The launch flags to be provided by the launch {@link Intent}. * @param expectedResult The expected result from the launch. */ - private void verifyStartActivityPreconditions(int preconditions, int launchFlags, + private void verifyStartActivityPreconditionsUntracked(int preconditions, int launchFlags, int expectedResult) { final ActivityTaskManagerService service = mService; final IPackageManager packageManager = mock(IPackageManager.class); @@ -329,9 +343,6 @@ public class ActivityStarterTests extends ActivityTestsBase { any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), anyBoolean(), anyBoolean(), any(), any(), any()); - // Use factory that only returns spy task. - mockTaskRecordFactory(); - if (mockGetLaunchStack) { // Instrument the stack and task used. final ActivityStack stack = mRootActivityContainer.getDefaultDisplay().createStack( @@ -482,7 +493,7 @@ public class ActivityStarterTests extends ActivityTestsBase { @Test public void testTaskModeViolation() { final ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); - ((TestActivityDisplay) display).removeAllTasks(); + display.removeAllTasks(); assertNoTasks(display); final ActivityStarter starter = prepareStarter(0); @@ -676,18 +687,27 @@ public class ActivityStarterTests extends ActivityTestsBase { doReturn(isCallingUidDeviceOwner).when(mService).isDeviceOwner(callingUid); final ActivityOptions options = spy(ActivityOptions.makeBasic()); + ActivityRecord[] outActivity = new ActivityRecord[1]; ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK) .setCallingPackage("com.whatever.dude") .setCaller(caller) .setCallingUid(callingUid) .setRealCallingUid(realCallingUid) - .setActivityOptions(new SafeActivityOptions(options)); + .setActivityOptions(new SafeActivityOptions(options)) + .setOutActivity(outActivity); final int result = starter.setReason("testBackgroundActivityStarts_" + name).execute(); assertEquals(ActivityStarter.getExternalResult( shouldHaveAborted ? START_ABORTED : START_SUCCESS), result); verify(options, times(shouldHaveAborted ? 1 : 0)).abort(); + + final ActivityRecord startedActivity = outActivity[0]; + if (startedActivity != null && startedActivity.getTaskRecord() != null) { + // Remove the activity so it doesn't interfere with with subsequent activity launch + // tests from this method. + startedActivity.getTaskRecord().removeActivity(startedActivity); + } } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index d8a9bb0d6237..297aa7eab169 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -45,8 +45,7 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { /** Verify that activity is finished correctly upon request. */ @Test public void testActivityFinish() { - final TestActivityStack stack = - (TestActivityStack) new StackBuilder(mRootActivityContainer).build(); + final ActivityStack stack = new StackBuilder(mRootActivityContainer).build(); final ActivityRecord activity = stack.getChildAt(0).getTopActivity(); assertTrue("Activity must be finished", mService.finishActivity(activity.appToken, 0 /* resultCode */, null /* resultData */, diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index ab2da2ba6c5f..a5dc24111dab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -16,96 +16,58 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; -import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; -import android.app.ActivityManagerInternal; import android.app.ActivityOptions; -import android.app.AppOpsManager; import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManagerGlobal; -import android.os.Handler; -import android.os.Looper; -import android.os.PowerManager; -import android.os.Process; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.testing.DexmakerShareClassLoaderRule; -import android.view.Display; import android.view.DisplayInfo; -import com.android.internal.app.IVoiceInteractor; import com.android.server.AttributeCache; -import com.android.server.ServiceThread; -import com.android.server.am.ActivityManagerService; -import com.android.server.am.PendingIntentController; -import com.android.server.appop.AppOpsService; -import com.android.server.firewall.IntentFirewall; -import com.android.server.policy.PermissionPolicyInternal; -import com.android.server.uri.UriGrantsManagerInternal; -import com.android.server.wm.TaskRecord.TaskRecordFactory; -import com.android.server.wm.utils.MockTracker; - -import org.junit.After; + import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; -import org.mockito.invocation.InvocationOnMock; - -import java.io.File; -import java.util.List; -import java.util.function.Consumer; /** * A base class to handle common operations in activity related unit tests. */ class ActivityTestsBase { - private static int sNextDisplayId = DEFAULT_DISPLAY + 1; @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); + @Rule + public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(); + final Context mContext = getInstrumentation().getTargetContext(); - final TestInjector mTestInjector = new TestInjector(mContext); ActivityTaskManagerService mService; RootActivityContainer mRootActivityContainer; ActivityStackSupervisor mSupervisor; - private MockTracker mMockTracker; - // Default package name static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo"; @@ -119,37 +81,18 @@ class ActivityTestsBase { @Before public void setUpBase() { - mMockTracker = new MockTracker(); - - mTestInjector.setUp(); - - mService = new TestActivityTaskManagerService(mContext); + mService = mSystemServicesTestRule.getActivityTaskManagerService(); mSupervisor = mService.mStackSupervisor; mRootActivityContainer = mService.mRootActivityContainer; } - @After - public void tearDownBase() { - mTestInjector.tearDown(); - if (mService != null) { - mService.setWindowManager(null); - mService = null; - } - if (sMockWindowManagerService != null) { - reset(sMockWindowManagerService); - } - - mMockTracker.close(); - mMockTracker = null; - } - /** Creates a {@link TestActivityDisplay}. */ TestActivityDisplay createNewActivityDisplay() { - return TestActivityDisplay.create(mSupervisor, sNextDisplayId++); + return TestActivityDisplay.create(mSupervisor); } TestActivityDisplay createNewActivityDisplay(DisplayInfo info) { - return TestActivityDisplay.create(mSupervisor, sNextDisplayId++, info); + return TestActivityDisplay.create(mSupervisor, info); } /** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */ @@ -166,25 +109,9 @@ class ActivityTestsBase { return display; } - /** - * Delegates task creation to {@link #TaskBuilder} to avoid the dependency of window hierarchy - * when starting activity in unit tests. - */ - void mockTaskRecordFactory(Consumer<TaskBuilder> taskBuilderSetup) { - final TaskBuilder taskBuilder = new TaskBuilder(mSupervisor).setCreateStack(false); - if (taskBuilderSetup != null) { - taskBuilderSetup.accept(taskBuilder); - } - final TaskRecord task = taskBuilder.build(); - final TaskRecordFactory factory = mock(TaskRecordFactory.class); - TaskRecord.setTaskRecordFactory(factory); - doReturn(task).when(factory).create(any() /* service */, anyInt() /* taskId */, - any() /* info */, any() /* intent */, any() /* voiceSession */, - any() /* voiceInteractor */); - } - - void mockTaskRecordFactory() { - mockTaskRecordFactory(null /* taskBuilderSetup */); + /** Sets the default minimum task size to 1 so that tests can use small task sizes */ + public void removeGlobalMinSizeRestriction() { + mService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp = 1; } /** @@ -204,6 +131,11 @@ class ActivityTestsBase { private ActivityStack mStack; private int mActivityFlags; private int mLaunchMode; + private int mResizeMode = RESIZE_MODE_RESIZEABLE; + private float mMaxAspectRatio; + private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + private boolean mLaunchTaskBehind; + private int mConfigChanges; ActivityBuilder(ActivityTaskManagerService service) { mService = service; @@ -254,6 +186,31 @@ class ActivityTestsBase { return this; } + ActivityBuilder setResizeMode(int resizeMode) { + mResizeMode = resizeMode; + return this; + } + + ActivityBuilder setMaxAspectRatio(float maxAspectRatio) { + mMaxAspectRatio = maxAspectRatio; + return this; + } + + ActivityBuilder setScreenOrientation(int screenOrientation) { + mScreenOrientation = screenOrientation; + return this; + } + + ActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) { + mLaunchTaskBehind = launchTaskBehind; + return this; + } + + ActivityBuilder setConfigChanges(int configChanges) { + mConfigChanges = configChanges; + return this; + } + ActivityRecord build() { if (mComponent == null) { final int id = sCurrentActivityId++; @@ -279,24 +236,31 @@ class ActivityTestsBase { } aInfo.flags |= mActivityFlags; aInfo.launchMode = mLaunchMode; + aInfo.resizeMode = mResizeMode; + aInfo.maxAspectRatio = mMaxAspectRatio; + aInfo.screenOrientation = mScreenOrientation; + aInfo.configChanges |= mConfigChanges; + + ActivityOptions options = null; + if (mLaunchTaskBehind) { + options = ActivityOptions.makeTaskLaunchBehind(); + } final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, 0 /* launchedFromPid */, 0, null, intent, null, aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */, 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */, - mService.mStackSupervisor, null /* options */, null /* sourceRecord */); + mService.mStackSupervisor, options, null /* sourceRecord */); spyOn(activity); - activity.mAppWindowToken = mock(AppWindowToken.class); - doCallRealMethod().when(activity.mAppWindowToken).getOrientationIgnoreVisibility(); - doCallRealMethod().when(activity.mAppWindowToken) - .setOrientation(anyInt(), any(), any()); - doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt()); - doNothing().when(activity).removeWindowContainer(); - doReturn(mock(Configuration.class)).when(activity.mAppWindowToken) - .getRequestedOverrideConfiguration(); - if (mTaskRecord != null) { - mTaskRecord.addActivityToTop(activity); + // fullscreen value is normally read from resources in ctor, so for testing we need + // to set it somewhere else since we can't mock resources. + activity.fullscreen = true; + activity.setTask(mTaskRecord); + activity.createAppWindowToken(); + spyOn(activity.mAppWindowToken); + // Make visible by default... + activity.mAppWindowToken.setHidden(false); } final WindowProcessController wpc = new WindowProcessController(mService, @@ -305,6 +269,9 @@ class ActivityTestsBase { mock(WindowProcessListener.class)); wpc.setThread(mock(IApplicationThread.class)); activity.setProcess(wpc); + + // Resume top activities to make sure all other signals in the system are connected. + mService.mRootActivityContainer.resumeFocusedStacksTopActivities(); return activity; } } @@ -313,16 +280,13 @@ class ActivityTestsBase { * Builder for creating new tasks. */ protected static class TaskBuilder { - // Default package name - static final String DEFAULT_PACKAGE = "com.bar"; - private final ActivityStackSupervisor mSupervisor; private ComponentName mComponent; private String mPackage; private int mFlags = 0; // Task id 0 is reserved in ARC for the home app. - private int mTaskId = 1; + private int mTaskId = SystemServicesTestRule.sNextTaskId++; private int mUserId = 0; private IVoiceInteractionSession mVoiceSession; private boolean mCreateStack = true; @@ -381,6 +345,7 @@ class ActivityTestsBase { if (mStack == null && mCreateStack) { mStack = mSupervisor.mRootActivityContainer.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + spyOn(mStack); } final ActivityInfo aInfo = new ActivityInfo(); @@ -396,450 +361,22 @@ class ActivityTestsBase { intent.setComponent(mComponent); intent.setFlags(mFlags); - final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo, + final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo, intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/); + spyOn(task); task.userId = mUserId; if (mStack != null) { mStack.moveToFront("test"); mStack.addTask(task, true, "creating test task"); - task.setStack(mStack); - task.setTask(); - mStack.getTaskStack().addChild(task.mTask, 0); + task.createTask(true, true); + spyOn(task.mTask); } task.touchActiveTime(); return task; } - - private static class TestTaskRecord extends TaskRecord { - TestTaskRecord(ActivityTaskManagerService service, int taskId, ActivityInfo info, - Intent intent, IVoiceInteractionSession voiceSession, - IVoiceInteractor voiceInteractor) { - super(service, taskId, info, intent, voiceSession, voiceInteractor); - } - - @Override - void createTask(boolean onTop, boolean showForAllUsers) { - setTask(); - } - - void setTask() { - Task mockTask = mock(Task.class); - mockTask.mTaskRecord = this; - doCallRealMethod().when(mockTask).onDescendantOrientationChanged(any(), any()); - setTask(mock(Task.class)); - } - } - } - - protected class TestActivityTaskManagerService extends ActivityTaskManagerService { - private PackageManagerInternal mPmInternal; - private PermissionPolicyInternal mPermissionPolicyInternal; - - // ActivityStackSupervisor may be created more than once while setting up AMS and ATMS. - // We keep the reference in order to prevent creating it twice. - ActivityStackSupervisor mTestStackSupervisor; - - ActivityDisplay mDefaultDisplay; - AppOpsService mAppOpsService; - - TestActivityTaskManagerService(Context context) { - super(context); - spyOn(this); - - mUgmInternal = mock(UriGrantsManagerInternal.class); - mAppOpsService = mock(AppOpsService.class); - - // Make sure permission checks aren't overridden. - doReturn(AppOpsManager.MODE_DEFAULT) - .when(mAppOpsService).noteOperation(anyInt(), anyInt(), anyString()); - - mSupportsMultiWindow = true; - mSupportsMultiDisplay = true; - mSupportsSplitScreenMultiWindow = true; - mSupportsFreeformWindowManagement = true; - mSupportsPictureInPicture = true; - - final TestActivityManagerService am = - new TestActivityManagerService(mTestInjector, this); - - spyOn(getLifecycleManager()); - spyOn(getLockTaskController()); - spyOn(getTaskChangeNotificationController()); - doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); - // allow background activity starts by default - doReturn(true).when(this).isBackgroundActivityStartsEnabled(); - doNothing().when(this).updateCpuStats(); - } - - void setup(IntentFirewall intentFirewall, PendingIntentController intentController, - ActivityManagerInternal amInternal, WindowManagerService wm, Looper looper) { - mAmInternal = amInternal; - initialize(intentFirewall, intentController, looper); - initRootActivityContainerMocks(wm); - setWindowManager(wm); - createDefaultDisplay(); - } - - void initRootActivityContainerMocks(WindowManagerService wm) { - spyOn(mRootActivityContainer); - mRootActivityContainer.setWindowContainer(mock(RootWindowContainer.class)); - mRootActivityContainer.mWindowManager = wm; - mRootActivityContainer.mDisplayManager = - (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); - doNothing().when(mRootActivityContainer).setWindowManager(any()); - // Invoked during {@link ActivityStack} creation. - doNothing().when(mRootActivityContainer).updateUIDsPresentOnDisplay(); - // Always keep things awake. - doReturn(true).when(mRootActivityContainer).hasAwakeDisplay(); - // Called when moving activity to pinned stack. - doNothing().when(mRootActivityContainer).ensureActivitiesVisible(any(), anyInt(), - anyBoolean()); - } - - void createDefaultDisplay() { - // Create a default display and put a home stack on it so that we'll always have - // something focusable. - mDefaultDisplay = TestActivityDisplay.create(mStackSupervisor, DEFAULT_DISPLAY); - spyOn(mDefaultDisplay); - mRootActivityContainer.addChild(mDefaultDisplay, ActivityDisplay.POSITION_TOP); - mDefaultDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); - final TaskRecord task = new TaskBuilder(mStackSupervisor) - .setStack(mDefaultDisplay.getHomeStack()).build(); - new ActivityBuilder(this).setTask(task).build(); - - doReturn(mDefaultDisplay).when(mRootActivityContainer).getDefaultDisplay(); - } - - @Override - int handleIncomingUser(int callingPid, int callingUid, int userId, String name) { - return userId; - } - - @Override - AppOpsService getAppOpsService() { - return mAppOpsService; - } - - @Override - void updateCpuStats() { - } - - @Override - void updateBatteryStats(ActivityRecord component, boolean resumed) { - } - - @Override - void updateActivityUsageStats(ActivityRecord activity, int event) { - } - - @Override - protected ActivityStackSupervisor createStackSupervisor() { - if (mTestStackSupervisor == null) { - mTestStackSupervisor = new TestActivityStackSupervisor(this, mH.getLooper()); - } - return mTestStackSupervisor; - } - - @Override - PackageManagerInternal getPackageManagerInternalLocked() { - if (mPmInternal == null) { - mPmInternal = mock(PackageManagerInternal.class); - doReturn(false) - .when(mPmInternal) - .isPermissionsReviewRequired(anyString(), anyInt()); - } - return mPmInternal; - } - - @Override - PermissionPolicyInternal getPermissionPolicyInternal() { - if (mPermissionPolicyInternal == null) { - mPermissionPolicyInternal = mock(PermissionPolicyInternal.class); - doReturn(true).when(mPermissionPolicyInternal).checkStartActivity(any(), anyInt(), - any()); - } - return mPermissionPolicyInternal; - } - } - - private static class TestInjector extends ActivityManagerService.Injector { - private ServiceThread mHandlerThread; - - TestInjector(Context context) { - super(context); - } - - @Override - public AppOpsService getAppOpsService(File file, Handler handler) { - return null; - } - - @Override - public Handler getUiHandler(ActivityManagerService service) { - return mHandlerThread.getThreadHandler(); - } - - @Override - public boolean isNetworkRestrictedForUid(int uid) { - return false; - } - - void setUp() { - mHandlerThread = new ServiceThread("ActivityTestsThread", - Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); - mHandlerThread.start(); - } - - void tearDown() { - // Make sure there are no running messages and then quit the thread so the next test - // won't be affected. - mHandlerThread.getThreadHandler().runWithScissors(mHandlerThread::quit, - 0 /* timeout */); - } - } - - // TODO: Replace this with a mock object since we are no longer in AMS package. - /** - * An {@link ActivityManagerService} subclass which provides a test - * {@link ActivityStackSupervisor}. - */ - class TestActivityManagerService extends ActivityManagerService { - - TestActivityManagerService(TestInjector testInjector, TestActivityTaskManagerService atm) { - super(testInjector, testInjector.mHandlerThread); - spyOn(this); - - mWindowManager = prepareMockWindowManager(); - mUgmInternal = mock(UriGrantsManagerInternal.class); - - atm.setup(mIntentFirewall, mPendingIntentController, new LocalService(), mWindowManager, - testInjector.mHandlerThread.getLooper()); - - mActivityTaskManager = atm; - mAtmInternal = atm.mInternal; - - doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); - PackageManagerInternal mockPackageManager = mock(PackageManagerInternal.class); - doReturn(mockPackageManager).when(this).getPackageManagerInternalLocked(); - doReturn(null).when(mockPackageManager).getDefaultHomeActivity(anyInt()); - doNothing().when(this).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt()); - } - } - - /** - * An {@link ActivityStackSupervisor} which stubs out certain methods that depend on - * setup not available in the test environment. Also specifies an injector for - */ - protected class TestActivityStackSupervisor extends ActivityStackSupervisor { - private KeyguardController mKeyguardController; - - TestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) { - super(service, looper); - spyOn(this); - mWindowManager = prepareMockWindowManager(); - mKeyguardController = mock(KeyguardController.class); - - // Do not schedule idle that may touch methods outside the scope of the test. - doNothing().when(this).scheduleIdleLocked(); - doNothing().when(this).scheduleIdleTimeoutLocked(any()); - // unit test version does not handle launch wake lock - doNothing().when(this).acquireLaunchWakelock(); - doReturn(mKeyguardController).when(this).getKeyguardController(); - - mLaunchingActivityWakeLock = mock(PowerManager.WakeLock.class); - - initialize(); - } - - @Override - public KeyguardController getKeyguardController() { - return mKeyguardController; - } - - @Override - void setWindowManager(WindowManagerService wm) { - mWindowManager = wm; - } - } - - protected static class TestActivityDisplay extends ActivityDisplay { - private final ActivityStackSupervisor mSupervisor; - - static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId) { - return create(supervisor, displayId, new DisplayInfo()); - } - - static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId, - DisplayInfo info) { - if (displayId == DEFAULT_DISPLAY) { - return new TestActivityDisplay(supervisor, - supervisor.mRootActivityContainer.mDisplayManager.getDisplay(displayId)); - } - final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, - info, DEFAULT_DISPLAY_ADJUSTMENTS); - return new TestActivityDisplay(supervisor, display); - } - - TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) { - super(supervisor.mService.mRootActivityContainer, display); - // Normally this comes from display-properties as exposed by WM. Without that, just - // hard-code to FULLSCREEN for tests. - setWindowingMode(WINDOWING_MODE_FULLSCREEN); - mSupervisor = supervisor; - } - - @SuppressWarnings("TypeParameterUnusedInFormals") - @Override - ActivityStack createStackUnchecked(int windowingMode, int activityType, - int stackId, boolean onTop) { - return new StackBuilder(mSupervisor.mRootActivityContainer).setDisplay(this) - .setWindowingMode(windowingMode).setActivityType(activityType) - .setStackId(stackId).setOnTop(onTop).setCreateActivity(false).build(); - } - - @Override - protected DisplayContent createDisplayContent() { - final DisplayContent displayContent = mock(DisplayContent.class); - DockedStackDividerController divider = mock(DockedStackDividerController.class); - doReturn(divider).when(displayContent).getDockedDividerController(); - return displayContent; - } - - void removeAllTasks() { - for (int i = 0; i < getChildCount(); i++) { - final ActivityStack stack = getChildAt(i); - for (TaskRecord task : (List<TaskRecord>) stack.getAllTasks()) { - stack.removeTask(task, "removeAllTasks", REMOVE_TASK_MODE_DESTROYING); - } - } - } - } - - private static WindowManagerService sMockWindowManagerService; - - private static WindowManagerService prepareMockWindowManager() { - if (sMockWindowManagerService == null) { - sMockWindowManagerService = mock(WindowManagerService.class); - } - - sMockWindowManagerService.mRoot = mock(RootWindowContainer.class); - - doAnswer((InvocationOnMock invocationOnMock) -> { - final Runnable runnable = invocationOnMock.<Runnable>getArgument(0); - if (runnable != null) { - runnable.run(); - } - return null; - }).when(sMockWindowManagerService).inSurfaceTransaction(any()); - - return sMockWindowManagerService; - } - - /** - * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a - * method is called. Note that its functionality depends on the implementations of the - * construction arguments. - */ - protected static class TestActivityStack - extends ActivityStack { - private int mOnActivityRemovedFromStackCount = 0; - - static final int IS_TRANSLUCENT_UNSET = 0; - static final int IS_TRANSLUCENT_FALSE = 1; - static final int IS_TRANSLUCENT_TRUE = 2; - private int mIsTranslucent = IS_TRANSLUCENT_UNSET; - - static final int SUPPORTS_SPLIT_SCREEN_UNSET = 0; - static final int SUPPORTS_SPLIT_SCREEN_FALSE = 1; - static final int SUPPORTS_SPLIT_SCREEN_TRUE = 2; - private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET; - - TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor, - int windowingMode, int activityType, boolean onTop, boolean createActivity) { - super(display, stackId, supervisor, windowingMode, activityType, onTop); - if (createActivity) { - new ActivityBuilder(mService).setCreateTask(true).setStack(this).build(); - if (onTop) { - // We move the task to front again in order to regain focus after activity - // added to the stack. Or {@link ActivityDisplay#mPreferredTopFocusableStack} - // could be other stacks (e.g. home stack). - moveToFront("createActivityStack"); - } else { - moveToBack("createActivityStack", null); - } - } - } - - @Override - void onActivityRemovedFromStack(ActivityRecord r) { - mOnActivityRemovedFromStackCount++; - super.onActivityRemovedFromStack(r); - } - - // Returns the number of times {@link #onActivityRemovedFromStack} has been called - int onActivityRemovedFromStackInvocationCount() { - return mOnActivityRemovedFromStackCount; - } - - @Override - protected void createTaskStack(int displayId, boolean onTop, Rect outBounds) { - mTaskStack = mock(TaskStack.class); - - // Primary pinned stacks require a non-empty out bounds to be set or else all tasks - // will be moved to the full screen stack. - if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - outBounds.set(0, 0, 100, 100); - } - } - - @Override - TaskStack getTaskStack() { - return mTaskStack; - } - - void setIsTranslucent(boolean isTranslucent) { - mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE; - } - - @Override - boolean isStackTranslucent(ActivityRecord starting) { - switch (mIsTranslucent) { - case IS_TRANSLUCENT_TRUE: - return true; - case IS_TRANSLUCENT_FALSE: - return false; - case IS_TRANSLUCENT_UNSET: - default: - return super.isStackTranslucent(starting); - } - } - - void setSupportsSplitScreen(boolean supportsSplitScreen) { - mSupportsSplitScreen = supportsSplitScreen - ? SUPPORTS_SPLIT_SCREEN_TRUE : SUPPORTS_SPLIT_SCREEN_FALSE; - } - - @Override - public boolean supportsSplitScreenWindowingMode() { - switch (mSupportsSplitScreen) { - case SUPPORTS_SPLIT_SCREEN_TRUE: - return true; - case SUPPORTS_SPLIT_SCREEN_FALSE: - return false; - case SUPPORTS_SPLIT_SCREEN_UNSET: - default: - return super.supportsSplitScreenWindowingMode(); - } - } - - @Override - void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, - boolean newTask, boolean keepCurTransition, - ActivityOptions options) { - } } static class StackBuilder { @@ -886,27 +423,45 @@ class ActivityTestsBase { return this; } - @SuppressWarnings("TypeParameterUnusedInFormals") ActivityStack build() { final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId(); + final ActivityStack stack; + final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor; if (mWindowingMode == WINDOWING_MODE_PINNED) { - return new ActivityStack(mDisplay, stackId, mRootActivityContainer.mStackSupervisor, + stack = new ActivityStack(mDisplay, stackId, supervisor, mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) { @Override Rect getDefaultPictureInPictureBounds(float aspectRatio) { return new Rect(50, 50, 100, 100); } - - @Override - void createTaskStack(int displayId, boolean onTop, Rect outBounds) { - mTaskStack = mock(TaskStack.class); - } }; } else { - return new TestActivityStack(mDisplay, stackId, - mRootActivityContainer.mStackSupervisor, mWindowingMode, - mActivityType, mOnTop, mCreateActivity); + stack = new ActivityStack(mDisplay, stackId, supervisor, + mWindowingMode, mActivityType, mOnTop); + + if (mCreateActivity) { + new ActivityBuilder(supervisor.mService) + .setCreateTask(true) + .setStack(stack) + .build(); + if (mOnTop) { + // We move the task to front again in order to regain focus after activity + // added to the stack. + // Or {@link ActivityDisplay#mPreferredTopFocusableStack} could be other + // stacks (e.g. home stack). + stack.moveToFront("createActivityStack"); + } else { + stack.moveToBack("createActivityStack", null); + } + } } + + spyOn(stack); + spyOn(stack.mTaskStack); + doNothing().when(stack).startActivityLocked( + any(), any(), anyBoolean(), anyBoolean(), any()); + + return stack; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 20379a2427be..e71c8f47a69f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -60,7 +60,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final AppWindowToken translucentOpening = createAppWindowToken(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - translucentOpening.setFillsParent(false); + translucentOpening.setOccludesParent(false); translucentOpening.setHidden(true); mDisplayContent.mOpeningApps.add(behind); mDisplayContent.mOpeningApps.add(translucentOpening); @@ -78,7 +78,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final AppWindowToken translucentClosing = createAppWindowToken(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - translucentClosing.setFillsParent(false); + translucentClosing.setOccludesParent(false); mDisplayContent.mClosingApps.add(translucentClosing); assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE, mAppTransitionController.maybeUpdateTransitToTranslucentAnim( @@ -94,7 +94,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final AppWindowToken translucentOpening = createAppWindowToken(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - translucentOpening.setFillsParent(false); + translucentOpening.setOccludesParent(false); translucentOpening.setHidden(true); mDisplayContent.mOpeningApps.add(behind); mDisplayContent.mOpeningApps.add(translucentOpening); @@ -110,10 +110,10 @@ public class AppTransitionControllerTest extends WindowTestsBase { synchronized (mWm.mGlobalLock) { final AppWindowToken opening = createAppWindowToken(mDisplayContent, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); - opening.setFillsParent(false); + opening.setOccludesParent(false); final AppWindowToken closing = createAppWindowToken(mDisplayContent, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); - closing.setFillsParent(false); + closing.setOccludesParent(false); Task task = opening.getTask(); mDisplayContent.mOpeningApps.add(opening); mDisplayContent.mClosingApps.add(closing); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index d1dc38273a28..c162b6a5a289 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -30,12 +30,14 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import android.graphics.Rect; import android.os.IBinder; @@ -195,6 +197,7 @@ public class AppTransitionTests extends WindowTestsBase { @Test public void testCancelRemoteAnimationWhenFreeze() { final DisplayContent dc = createNewDisplay(Display.STATE_ON); + doReturn(false).when(dc).onDescendantOrientationChanged(any(), any()); final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "exiting app"); final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken; diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java index d9566a3c871d..e387e186182b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -36,7 +36,7 @@ import static android.view.WindowManager.TRANSIT_UNSET; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; @@ -152,10 +152,6 @@ public class AppWindowTokenTests extends WindowTestsBase { @Test @FlakyTest(bugId = 131005232) public void testLandscapeSeascapeRotationByApp() { - // Some plumbing to get the service ready for rotation updates. - mWm.mDisplayReady = true; - mWm.mDisplayEnabled = true; - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( TYPE_BASE_APPLICATION); attrs.setTitle("AppWindow"); @@ -185,25 +181,21 @@ public class AppWindowTokenTests extends WindowTestsBase { @Test public void testLandscapeSeascapeRotationByPolicy() { - // Some plumbing to get the service ready for rotation updates. - mWm.mDisplayReady = true; - mWm.mDisplayEnabled = true; - - final DisplayRotation spiedRotation = spy(mDisplayContent.getDisplayRotation()); - mDisplayContent.setDisplayRotation(spiedRotation); + final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); + spyOn(displayRotation); final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( TYPE_BASE_APPLICATION); - attrs.setTitle("AppWindow"); + attrs.setTitle("RotationByPolicy"); final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken); mToken.addWindow(appWindow); // Set initial orientation and update. - performRotation(spiedRotation, Surface.ROTATION_90); + performRotation(displayRotation, Surface.ROTATION_90); appWindow.mResizeReported = false; // Update the rotation to perform 180 degree rotation and check that resize was reported. - performRotation(spiedRotation, Surface.ROTATION_270); + performRotation(displayRotation, Surface.ROTATION_270); assertTrue(appWindow.mResizeReported); appWindow.removeImmediately(); @@ -211,14 +203,7 @@ public class AppWindowTokenTests extends WindowTestsBase { private void performRotation(DisplayRotation spiedRotation, int rotationToReport) { doReturn(rotationToReport).when(spiedRotation).rotationForOrientation(anyInt(), anyInt()); - int oldRotation = mDisplayContent.getRotation(); mWm.updateRotation(false, false); - // Must manually apply here since ATM doesn't know about the display during this test - // (meaning it can't perform the normal sendNewConfiguration flow). - mDisplayContent.applyRotationLocked(oldRotation, mDisplayContent.getRotation()); - // Prevent the next rotation from being deferred by animation. - mWm.mAnimator.setScreenRotationAnimationLocked(mDisplayContent.getDisplayId(), null); - mWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); } @Test @@ -266,14 +251,14 @@ public class AppWindowTokenTests extends WindowTestsBase { public void testGetOrientation() { mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - mToken.setFillsParent(false); - // Can specify orientation if app doesn't fill parent. + mToken.setOccludesParent(false); + // Can specify orientation if app doesn't occludes parent. assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation()); - mToken.setFillsParent(true); + mToken.setOccludesParent(true); mToken.setHidden(true); mToken.sendingToBottom = true; - // Can not specify orientation if app isn't visible even though it fills parent. + // Can not specify orientation if app isn't visible even though it occludes parent. assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation()); // Can specify orientation if the current orientation candidate is orientation behind. assertEquals(SCREEN_ORIENTATION_LANDSCAPE, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 388658dfac88..62897687b8ad 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -83,7 +83,6 @@ import android.view.test.InsetsModeSession; import androidx.test.filters.SmallTest; -import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.server.wm.utils.WmDisplayCutout; @@ -653,25 +652,27 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testOnDescendantOrientationRequestChanged() { final DisplayContent dc = createNewDisplay(); - mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class); + dc.getDisplayRotation().setFixedToUserRotation( + DisplayRotation.FIXED_TO_USER_ROTATION_DISABLED); final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); - window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS); - window.mAppToken.setOrientation(newOrientation); - - ActivityRecord activityRecord = mock(ActivityRecord.class); + final ActivityStack stack = + new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer) + .setDisplay(dc.mAcitvityDisplay).build(); + final ActivityRecord activity = stack.topTask().getTopActivity(); - assertTrue("Display should rotate to handle orientation request by default.", - dc.onDescendantOrientationChanged(window.mToken.token, activityRecord)); + activity.setRequestedOrientation(newOrientation); final ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class); verify(dc.mAcitvityDisplay).updateDisplayOverrideConfigurationLocked(captor.capture(), - same(activityRecord), anyBoolean(), same(null)); + same(activity), anyBoolean(), same(null)); final Configuration newDisplayConfig = captor.getValue(); - assertEquals(Configuration.ORIENTATION_PORTRAIT, newDisplayConfig.orientation); + final int expectedOrientation = newOrientation == SCREEN_ORIENTATION_PORTRAIT + ? Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + assertEquals(expectedOrientation, newDisplayConfig.orientation); } @Test @@ -679,22 +680,20 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.getDisplayRotation().setFixedToUserRotation( DisplayRotation.FIXED_TO_USER_ROTATION_ENABLED); - mWm.mAtmService.mRootActivityContainer = mock(RootActivityContainer.class); final int newOrientation = dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); - window.getTask().mTaskRecord = mock(TaskRecord.class, ExtendedMockito.RETURNS_DEEP_STUBS); - window.mAppToken.setOrientation(newOrientation); + final ActivityStack stack = + new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer) + .setDisplay(dc.mAcitvityDisplay).build(); + final ActivityRecord activity = stack.topTask().getTopActivity(); - ActivityRecord activityRecord = mock(ActivityRecord.class); + activity.setRequestedOrientation(newOrientation); - assertFalse("Display shouldn't rotate to handle orientation request if fixed to" - + " user rotation.", - dc.onDescendantOrientationChanged(window.mToken.token, activityRecord)); verify(dc.mAcitvityDisplay, never()).updateDisplayOverrideConfigurationLocked(any(), - eq(activityRecord), anyBoolean(), same(null)); + eq(activity), anyBoolean(), same(null)); + assertEquals(dc.getDisplayRotation().getUserRotation(), dc.getRotation()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java index bfede51f5a3c..f6f8811f2317 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java @@ -95,7 +95,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { public void setUp() throws Exception { deleteRecursively(TEST_FOLDER); - mWm.setSupportsFreeformWindowManagement(false); + mWm.mAtmService.mSupportsFreeformWindowManagement = false; mWm.setIsPc(false); mWm.setForceDesktopModeOnExternalDisplays(false); @@ -134,7 +134,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { @Test public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_NoDesktopMode() { - mWm.setSupportsFreeformWindowManagement(true); + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); @@ -144,7 +144,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { @Test public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_HasDesktopMode() { - mWm.setSupportsFreeformWindowManagement(true); + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mWm.setForceDesktopModeOnExternalDisplays(true); mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); @@ -155,7 +155,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { @Test public void testPrimaryDisplayDefaultToFreeform_HasFreeformSupport_IsPc() { - mWm.setSupportsFreeformWindowManagement(true); + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mWm.setIsPc(true); mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); @@ -168,7 +168,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { public void testPrimaryDisplayUpdateToFreeform_HasFreeformSupport_IsPc() { mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); - mWm.setSupportsFreeformWindowManagement(true); + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mWm.setIsPc(true); mTarget.updateSettingsForDisplay(mPrimaryDisplay); @@ -187,7 +187,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { @Test public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_NonPc_NoDesktopMode() { - mWm.setSupportsFreeformWindowManagement(true); + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); @@ -197,7 +197,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { @Test public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_NonPc_HasDesktopMode() { - mWm.setSupportsFreeformWindowManagement(true); + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mWm.setForceDesktopModeOnExternalDisplays(true); mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); @@ -208,7 +208,7 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { @Test public void testSecondaryDisplayDefaultToFreeform_HasFreeformSupport_IsPc() { - mWm.setSupportsFreeformWindowManagement(true); + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mWm.setIsPc(true); mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java index b28ae40d5056..be2ee2909a22 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java @@ -331,7 +331,9 @@ public class LaunchParamsControllerTests extends ActivityTestsBase { mController.layoutTask(task, null /* windowLayout */); - assertEquals(expected, task.getBounds()); + // TaskRecord will make adjustments to requested bounds. We only need to guarantee that the + // reuqested bounds are expected. + assertEquals(expected, task.getRequestedOverrideBounds()); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index e4d3770de54c..49d778f023e5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -104,6 +104,7 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { mDisplayUniqueId = "test:" + Integer.toString(sNextUniqueId++); final DisplayInfo info = new DisplayInfo(); + mService.mContext.getDisplay().getDisplayInfo(info); info.uniqueId = mDisplayUniqueId; mTestDisplay = createNewActivityDisplay(info); mRootActivityContainer.addChild(mTestDisplay, ActivityDisplay.POSITION_TOP); diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java index 63d9fb9c17e9..efd468f1f77a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java @@ -61,7 +61,7 @@ public class PinnedStackControllerTest extends WindowTestsBase { @Test public void setShelfHeight_shelfVisibilityChangedTriggered() throws RemoteException { - mWm.mSupportsPictureInPicture = true; + mWm.mAtmService.mSupportsPictureInPicture = true; mWm.registerPinnedStackListener(DEFAULT_DISPLAY, mIPinnedStackListener); verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index b7a85d7bb5b7..fb4e330f06ed 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -32,7 +32,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -53,14 +53,12 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.app.WindowConfiguration; import android.content.ComponentName; -import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; -import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; @@ -93,11 +91,8 @@ public class RecentTasksTest extends ActivityTestsBase { private static final int TEST_QUIET_USER_ID = 20; private static final UserInfo DEFAULT_USER_INFO = new UserInfo(); private static final UserInfo QUIET_USER_INFO = new UserInfo(); - private static int sLastTaskId = 1; - private static int sLastStackId = 1; private static final int INVALID_STACK_ID = 999; - private TestActivityTaskManagerService mTestService; private ActivityDisplay mDisplay; private ActivityDisplay mOtherDisplay; private ActivityDisplay mSingleTaskDisplay; @@ -115,13 +110,29 @@ public class RecentTasksTest extends ActivityTestsBase { @Before public void setUp() throws Exception { mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); - mTestService = new MyTestActivityTaskManagerService(mContext); - mRecentTasks = (TestRecentTasks) mTestService.getRecentTasks(); + + // Set testing displays + mDisplay = mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY); + mOtherDisplay = createNewActivityDisplay(); + mSingleTaskDisplay = createNewActivityDisplay(); + mSingleTaskDisplay.setDisplayToSingleTaskInstance(); + mRootActivityContainer.addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP); + mRootActivityContainer.addChild(mDisplay, ActivityDisplay.POSITION_TOP); + mRootActivityContainer.addChild(mSingleTaskDisplay, ActivityDisplay.POSITION_TOP); + + // Set the recent tasks we should use for testing in this class. + mRecentTasks = new TestRecentTasks(mService, mTaskPersister); + spyOn(mRecentTasks); + mService.setRecentTasks(mRecentTasks); mRecentTasks.loadParametersFromResources(mContext.getResources()); - mRunningTasks = (TestRunningTasks) mTestService.mStackSupervisor.mRunningTasks; - mHomeStack = mTestService.mRootActivityContainer.getDefaultDisplay().getOrCreateStack( + + // Set the running tasks we should use for testing in this class. + mRunningTasks = new TestRunningTasks(); + mService.mStackSupervisor.setRunningTasks(mRunningTasks); + + mHomeStack = mDisplay.getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - mStack = mTestService.mRootActivityContainer.getDefaultDisplay().createStack( + mStack = mDisplay.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mCallbacksRecorder = new CallbacksRecorder(); mRecentTasks.registerCallback(mCallbacksRecorder); @@ -723,7 +734,7 @@ public class RecentTasksTest extends ActivityTestsBase { ActivityStack stack = mTasks.get(2).getStack(); stack.moveToFront("", mTasks.get(2)); - doReturn(stack).when(mTestService.mRootActivityContainer).getTopDisplayFocusedStack(); + doReturn(stack).when(mService.mRootActivityContainer).getTopDisplayFocusedStack(); // Simulate the reset from the timeout mRecentTasks.resetFreezeTaskListReorderingOnTimeout(); @@ -742,10 +753,9 @@ public class RecentTasksTest extends ActivityTestsBase { public void testBackStackTasks_expectNoTrim() { mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); - final MyTestActivityStackSupervisor supervisor = - (MyTestActivityStackSupervisor) mTestService.mStackSupervisor; final ActivityStack homeStack = mDisplay.getHomeStack(); - final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack aboveHomeStack = mDisplay.createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all // the tasks belong in stacks above the home stack @@ -761,11 +771,11 @@ public class RecentTasksTest extends ActivityTestsBase { public void testBehindHomeStackTasks_expectTaskTrimmed() { mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); - final MyTestActivityStackSupervisor supervisor = - (MyTestActivityStackSupervisor) mTestService.mStackSupervisor; - final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack behindHomeStack = mDisplay.createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityStack homeStack = mDisplay.getHomeStack(); - final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack aboveHomeStack = mDisplay.createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind // the home stack is trimmed once a new task is added @@ -783,10 +793,9 @@ public class RecentTasksTest extends ActivityTestsBase { public void testOtherDisplayTasks_expectNoTrim() { mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); - final MyTestActivityStackSupervisor supervisor = - (MyTestActivityStackSupervisor) mTestService.mStackSupervisor; final ActivityStack homeStack = mDisplay.getHomeStack(); - final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor); + final ActivityStack otherDisplayStack = mOtherDisplay.createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not // removed @@ -887,16 +896,16 @@ public class RecentTasksTest extends ActivityTestsBase { mStack.remove(); // The following APIs should not restore task from recents to the active list. - assertNotRestoreTask(() -> mTestService.setFocusedTask(taskId)); - assertNotRestoreTask(() -> mTestService.startSystemLockTaskMode(taskId)); - assertNotRestoreTask(() -> mTestService.cancelTaskWindowTransition(taskId)); + assertNotRestoreTask(() -> mService.setFocusedTask(taskId)); + assertNotRestoreTask(() -> mService.startSystemLockTaskMode(taskId)); + assertNotRestoreTask(() -> mService.cancelTaskWindowTransition(taskId)); assertNotRestoreTask( - () -> mTestService.resizeTask(taskId, null /* bounds */, 0 /* resizeMode */)); + () -> mService.resizeTask(taskId, null /* bounds */, 0 /* resizeMode */)); assertNotRestoreTask( - () -> mTestService.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN, + () -> mService.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN, false/* toTop */)); assertNotRestoreTask( - () -> mTestService.setTaskWindowingModeSplitScreenPrimary(taskId, + () -> mService.setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, false /* toTop */, false /* animate */, null /* initialBounds */, true /* showRecents */)); @@ -910,7 +919,7 @@ public class RecentTasksTest extends ActivityTestsBase { mRecentTasks.remove(task); TaskChangeNotificationController controller = - mTestService.getTaskChangeNotificationController(); + mService.getTaskChangeNotificationController(); verify(controller, times(2)).notifyTaskListUpdated(); } @@ -923,7 +932,7 @@ public class RecentTasksTest extends ActivityTestsBase { // 2 calls - Once for add and once for remove TaskChangeNotificationController controller = - mTestService.getTaskChangeNotificationController(); + mService.getTaskChangeNotificationController(); verify(controller, times(2)).notifyTaskListUpdated(); } @@ -938,7 +947,7 @@ public class RecentTasksTest extends ActivityTestsBase { // 4 calls - Twice for add and twice for remove TaskChangeNotificationController controller = - mTestService.getTaskChangeNotificationController(); + mService.getTaskChangeNotificationController(); verify(controller, times(4)).notifyTaskListUpdated(); } @@ -980,7 +989,7 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testNotRecentsComponent_denyApiAccess() throws Exception { - doReturn(PackageManager.PERMISSION_DENIED).when(mTestService) + doReturn(PackageManager.PERMISSION_DENIED).when(mService) .checkGetTasksPermission(anyString(), anyInt(), anyInt()); // Expect the following methods to fail due to recents component not being set mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION); @@ -992,7 +1001,7 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testRecentsComponent_allowApiAccessWithoutPermissions() { - doReturn(PackageManager.PERMISSION_DENIED).when(mTestService) + doReturn(PackageManager.PERMISSION_DENIED).when(mService) .checkGetTasksPermission(anyString(), anyInt(), anyInt()); // Set the recents component and ensure that the following calls do not fail @@ -1002,62 +1011,62 @@ public class RecentTasksTest extends ActivityTestsBase { } private void doTestRecentTasksApis(boolean expectCallable) { - assertSecurityException(expectCallable, () -> mTestService.removeStack(INVALID_STACK_ID)); + assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID)); assertSecurityException(expectCallable, - () -> mTestService.removeStacksInWindowingModes( + () -> mService.removeStacksInWindowingModes( new int[]{WINDOWING_MODE_UNDEFINED})); assertSecurityException(expectCallable, - () -> mTestService.removeStacksWithActivityTypes( + () -> mService.removeStacksWithActivityTypes( new int[]{ACTIVITY_TYPE_UNDEFINED})); - assertSecurityException(expectCallable, () -> mTestService.removeTask(0)); + assertSecurityException(expectCallable, () -> mService.removeTask(0)); assertSecurityException(expectCallable, - () -> mTestService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true)); + () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true)); assertSecurityException(expectCallable, - () -> mTestService.moveTaskToStack(0, INVALID_STACK_ID, true)); + () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true)); assertSecurityException(expectCallable, - () -> mTestService.setTaskWindowingModeSplitScreenPrimary(0, + () -> mService.setTaskWindowingModeSplitScreenPrimary(0, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true)); - assertSecurityException(expectCallable, () -> mTestService.dismissSplitScreenMode(true)); - assertSecurityException(expectCallable, () -> mTestService.dismissPip(true, 0)); + assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true)); + assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0)); assertSecurityException(expectCallable, - () -> mTestService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect())); + () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect())); assertSecurityException(expectCallable, - () -> mTestService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0)); + () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0)); assertSecurityException(expectCallable, - () -> mTestService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(), + () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(), new Rect())); assertSecurityException(expectCallable, - () -> mTestService.resizePinnedStack(new Rect(), new Rect())); - assertSecurityException(expectCallable, () -> mTestService.getAllStackInfos()); + () -> mService.resizePinnedStack(new Rect(), new Rect())); + assertSecurityException(expectCallable, () -> mService.getAllStackInfos()); assertSecurityException(expectCallable, - () -> mTestService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED)); + () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED)); assertSecurityException(expectCallable, () -> { try { - mTestService.getFocusedStackInfo(); + mService.getFocusedStackInfo(); } catch (RemoteException e) { // Ignore } }); assertSecurityException(expectCallable, - () -> mTestService.moveTasksToFullscreenStack(INVALID_STACK_ID, true)); + () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true)); assertSecurityException(expectCallable, - () -> mTestService.startActivityFromRecents(0, new Bundle())); - assertSecurityException(expectCallable, () -> mTestService.getTaskSnapshot(0, true)); - assertSecurityException(expectCallable, () -> mTestService.registerTaskStackListener(null)); + () -> mService.startActivityFromRecents(0, new Bundle())); + assertSecurityException(expectCallable, () -> mService.getTaskSnapshot(0, true)); + assertSecurityException(expectCallable, () -> mService.registerTaskStackListener(null)); assertSecurityException(expectCallable, - () -> mTestService.unregisterTaskStackListener(null)); - assertSecurityException(expectCallable, () -> mTestService.getTaskDescription(0)); - assertSecurityException(expectCallable, () -> mTestService.cancelTaskWindowTransition(0)); - assertSecurityException(expectCallable, () -> mTestService.startRecentsActivity(null, null, + () -> mService.unregisterTaskStackListener(null)); + assertSecurityException(expectCallable, () -> mService.getTaskDescription(0)); + assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0)); + assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, null)); - assertSecurityException(expectCallable, () -> mTestService.cancelRecentsAnimation(true)); - assertSecurityException(expectCallable, () -> mTestService.stopAppSwitches()); - assertSecurityException(expectCallable, () -> mTestService.resumeAppSwitches()); + assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true)); + assertSecurityException(expectCallable, () -> mService.stopAppSwitches()); + assertSecurityException(expectCallable, () -> mService.resumeAppSwitches()); } private void testGetTasksApis(boolean expectCallable) { - mTestService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID); - mTestService.getTasks(MAX_VALUE); + mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID); + mService.getTasks(MAX_VALUE); if (expectCallable) { assertTrue(mRecentTasks.mLastAllowed); assertTrue(mRunningTasks.mLastAllowed); @@ -1072,10 +1081,9 @@ public class RecentTasksTest extends ActivityTestsBase { } private TaskBuilder createTaskBuilder(String packageName, String className) { - return new TaskBuilder(mTestService.mStackSupervisor) + return new TaskBuilder(mService.mStackSupervisor) .setComponent(new ComponentName(packageName, className)) .setStack(mStack) - .setTaskId(sLastTaskId++) .setUserId(TEST_USER_0_ID); } @@ -1140,68 +1148,6 @@ public class RecentTasksTest extends ActivityTestsBase { } } - private class MyTestActivityTaskManagerService extends TestActivityTaskManagerService { - MyTestActivityTaskManagerService(Context context) { - super(context); - } - - @Override - protected RecentTasks createRecentTasks() { - return spy(new TestRecentTasks(this, mTaskPersister)); - } - - @Override - protected ActivityStackSupervisor createStackSupervisor() { - if (mTestStackSupervisor == null) { - mTestStackSupervisor = new MyTestActivityStackSupervisor(this, mH.getLooper()); - } - return mTestStackSupervisor; - } - - @Override - void createDefaultDisplay() { - super.createDefaultDisplay(); - mDisplay = mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY); - mOtherDisplay = TestActivityDisplay.create(mTestStackSupervisor, DEFAULT_DISPLAY + 1); - mSingleTaskDisplay = TestActivityDisplay.create(mTestStackSupervisor, - DEFAULT_DISPLAY + 2); - mSingleTaskDisplay.setDisplayToSingleTaskInstance(); - mRootActivityContainer.addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP); - mRootActivityContainer.addChild(mDisplay, ActivityDisplay.POSITION_TOP); - mRootActivityContainer.addChild(mSingleTaskDisplay, ActivityDisplay.POSITION_TOP); - } - } - - private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor { - MyTestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) { - super(service, looper); - } - - @Override - RunningTasks createRunningTasks() { - mRunningTasks = new TestRunningTasks(); - return mRunningTasks; - } - } - - private static class MyTestActivityStack extends TestActivityStack { - private ActivityDisplay mDisplay = null; - - MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) { - super(display, sLastStackId++, supervisor, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, true /* onTop */, false /* createActivity */); - mDisplay = display; - } - - @Override - ActivityDisplay getDisplay() { - if (mDisplay != null) { - return mDisplay; - } - return super.getDisplay(); - } - } - private static class CallbacksRecorder implements Callbacks { public final ArrayList<TaskRecord> mAdded = new ArrayList<>(); public final ArrayList<TaskRecord> mTrimmed = new ArrayList<>(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index f5a1d752590e..9ca0180e507d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; @@ -132,8 +133,14 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testIncludedApps_expectTargetAndVisible() { mWm.setRecentsAnimationController(mController); - final AppWindowToken homeAppWindow = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); + final ActivityStack homStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final AppWindowToken homeAppWindow = + new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) + .setStack(homStack) + .setCreateTask(true) + .build() + .mAppWindowToken; final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final AppWindowToken hiddenAppWindow = createAppWindowToken(mDisplayContent, @@ -169,7 +176,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Simulate the app transition finishing mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0, 0, 0); - verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true, false); + verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); } @Test @@ -201,7 +208,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { spyOn(mController.mRecentScreenshotAnimator.mAnimatable); mController.mRecentScreenshotAnimator.cancelAnimation(); verify(mController.mRecentScreenshotAnimator.mAnimatable).onAnimationLeashLost(any()); - verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true, false); + verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 0e119e3cc375..dcc295c4aab3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -74,8 +74,9 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Before public void setUp() throws Exception { mRecentsAnimationController = mock(RecentsAnimationController.class); - doReturn(mRecentsAnimationController).when( - mService.mWindowManager).getRecentsAnimationController(); + mService.mWindowManager.setRecentsAnimationController(mRecentsAnimationController); + doNothing().when(mService.mWindowManager).initializeRecentsAnimation( + anyInt(), any(), any(), anyInt(), any()); doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation(); final RecentTasks recentTasks = mService.getRecentTasks(); @@ -107,16 +108,25 @@ public class RecentsAnimationTest extends ActivityTestsBase { assertTrue(recentActivity.visible); // Simulate the animation is cancelled without changing the stack order. - recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, true /* runSychronously */, - false /* sendUserLeaveHint */); + recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */); // The non-top recents activity should be invisible by the restored launch-behind state. assertFalse(recentActivity.visible); } @Test public void testPreloadRecentsActivity() { - // Ensure that the fake recent component can be resolved by the recents intent. - mockTaskRecordFactory(builder -> builder.setComponent(mRecentsComponent)); + final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay(); + final ActivityStack homeStack = + defaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); + defaultDisplay.positionChildAtTop(homeStack, false /* includingParents */); + ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked(); + if (topRunningHomeActivity == null) { + topRunningHomeActivity = new ActivityBuilder(mService) + .setStack(homeStack) + .setCreateTask(true) + .build(); + } + ActivityInfo aInfo = new ActivityInfo(); aInfo.applicationInfo = new ApplicationInfo(); aInfo.applicationInfo.uid = 10001; @@ -204,6 +214,13 @@ public class RecentsAnimationTest extends ActivityTestsBase { ActivityStack homeStack = display.getHomeStack(); // Assume the home activity support recents. ActivityRecord targetActivity = homeStack.getTopActivity(); + if (targetActivity == null) { + targetActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .setStack(homeStack) + .build(); + } + // Put another home activity in home stack. ActivityRecord anotherHomeActivity = new ActivityBuilder(mService) .setComponent(new ComponentName(mContext.getPackageName(), "Home2")) @@ -226,13 +243,12 @@ public class RecentsAnimationTest extends ActivityTestsBase { anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome"); // The current top activity is not the recents so the animation should be canceled. - verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously( + verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( eq(REORDER_KEEP_IN_PLACE), any() /* reason */); // The test uses mocked RecentsAnimationController so we have to invoke the callback // manually to simulate the flow. - recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, true /* runSychronously */, - false /* sendUserLeaveHint */); + recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */); // We should restore the launch-behind of the original target activity. assertFalse(targetActivity.mLaunchTaskBehind); } @@ -269,7 +285,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { fullscreenStack.moveToFront("Activity start"); // Ensure that the recents animation was canceled by cancelAnimationSynchronously(). - verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously( + verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( eq(REORDER_KEEP_IN_PLACE), any()); // Assume recents animation already started, set a state that cancel recents animation @@ -314,7 +330,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { fullscreenStack.remove(); // Ensure that the recents animation was NOT canceled - verify(mService.mWindowManager, times(0)).cancelRecentsAnimationSynchronously( + verify(mService.mWindowManager, times(0)).cancelRecentsAnimation( eq(REORDER_KEEP_IN_PLACE), any()); verify(mRecentsAnimationController, times(0)).setCancelOnNextTransitionStart(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index d4f24f9b5717..539a79ccd783 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -97,7 +97,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testRestoringInvalidTask() { - ((TestActivityDisplay) mRootActivityContainer.getDefaultDisplay()).removeAllTasks(); + mRootActivityContainer.getDefaultDisplay().removeAllTasks(); TaskRecord task = mRootActivityContainer.anyTaskForId(0 /*taskId*/, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */); assertNull(task); @@ -304,21 +304,23 @@ public class RootActivityContainerTests extends ActivityTestsBase { */ @Test public void testResizeDockedStackForSplitScreenPrimary() { - final Rect taskSize = new Rect(0, 0, 600, 600); + final Rect taskSize = new Rect(0, 0, 1000, 1000); final Rect stackSize = new Rect(0, 0, 300, 300); // Create primary split-screen stack with a task. - final ActivityStack primaryStack = mRootActivityContainer.getDefaultDisplay() - .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TaskRecord task = new TaskBuilder(mSupervisor).setStack(primaryStack).build(); + final ActivityStack primaryStack = new StackBuilder(mRootActivityContainer) + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) + .setOnTop(true) + .build(); + final TaskRecord task = primaryStack.topTask(); // Resize dock stack. mService.resizeDockedStack(stackSize, taskSize, null, null, null); // Verify dock stack & its task bounds if is equal as resized result. - assertEquals(primaryStack.getBounds(), stackSize); - assertEquals(task.getBounds(), taskSize); + assertEquals(stackSize, primaryStack.getBounds()); + assertEquals(taskSize, task.getBounds()); } /** @@ -328,8 +330,9 @@ public class RootActivityContainerTests extends ActivityTestsBase { public void testFindTaskToMoveToFrontWhenRecentsOnTop() { // Create stack/task on default display. final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); - final TestActivityStack targetStack = (TestActivityStack) new StackBuilder( - mRootActivityContainer).setOnTop(false).build(); + final ActivityStack targetStack = new StackBuilder(mRootActivityContainer) + .setOnTop(false) + .build(); final TaskRecord targetTask = targetStack.getChildAt(0); // Create Recents on top of the display. @@ -505,12 +508,10 @@ public class RootActivityContainerTests extends ActivityTestsBase { mockResolveSecondaryHomeActivity(); // Create secondary displays. - final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay()); + final TestActivityDisplay secondDisplay = createNewActivityDisplay(); mRootActivityContainer.addChild(secondDisplay, POSITION_TOP); doReturn(true).when(secondDisplay).supportsSystemDecorations(); - // Create mock tasks and other necessary mocks. - mockTaskRecordFactory(); doReturn(true).when(mRootActivityContainer) .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean()); doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay( @@ -621,7 +622,6 @@ public class RootActivityContainerTests extends ActivityTestsBase { info.applicationInfo.packageName = "android"; info.name = ResolverActivity.class.getName(); doReturn(info).when(mRootActivityContainer).resolveHomeActivity(anyInt(), any()); - mockTaskRecordFactory(); mRootActivityContainer.startHomeOnDisplay(0 /* userId */, "test", DEFAULT_DISPLAY); final ActivityRecord resolverActivity = mRootActivityContainer.topRunningActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index f51ce131b7bc..db105ddc956d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -96,7 +96,7 @@ public class RootWindowContainerTests extends WindowTestsBase { mWm.getDefaultDisplayContentLocked().getWindowingMode()); mWm.mIsPc = true; - mWm.mSupportsFreeformWindowManagement = true; + mWm.mAtmService.mSupportsFreeformWindowManagement = true; mWm.mRoot.onSettingsRetrieved(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index df7c9a44dd12..1ad0e00bbd48 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.os.Process.THREAD_PRIORITY_DEFAULT; import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; import static android.view.Display.DEFAULT_DISPLAY; @@ -25,67 +28,91 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.nullable; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; +import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.IntentFilter; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.net.Uri; import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.UserHandle; -import android.view.Display; +import android.provider.DeviceConfig; import android.view.InputChannel; +import android.view.Surface; +import android.view.SurfaceControl; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.server.AnimationThread; +import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.LockGuard; +import com.android.server.ServiceThread; import com.android.server.Watchdog; +import com.android.server.am.ActivityManagerService; +import com.android.server.appop.AppOpsService; +import com.android.server.display.color.ColorDisplayService; import com.android.server.input.InputManagerService; +import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.WindowManagerPolicy; -import com.android.server.wm.utils.MockTracker; +import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.uri.UriGrantsManagerInternal; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import org.mockito.invocation.InvocationOnMock; +import org.mockito.Mockito; import org.mockito.quality.Strictness; +import java.io.File; import java.util.concurrent.atomic.AtomicBoolean; /** - * JUnit test rule to create a mock {@link WindowManagerService} instance for tests. + * JUnit test rule to correctly setting up system services like {@link WindowManagerService} + * and {@link ActivityTaskManagerService} for tests. */ public class SystemServicesTestRule implements TestRule { private static final String TAG = SystemServicesTestRule.class.getSimpleName(); + static int sNextDisplayId = DEFAULT_DISPLAY + 100; + static int sNextTaskId = 100; + private final AtomicBoolean mCurrentMessagesProcessed = new AtomicBoolean(false); - private MockTracker mMockTracker; + private Context mContext; private StaticMockitoSession mMockitoSession; - private WindowManagerService mWindowManagerService; - private TestWindowManagerPolicy mWindowManagerPolicy; - - /** {@link MockTracker} to track mocks created by {@link SystemServicesTestRule}. */ - private static class Tracker extends MockTracker { - // This empty extended class is necessary since Mockito distinguishes a listener by it - // class. - } + ServiceThread mHandlerThread; + private ActivityManagerService mAmService; + private ActivityTaskManagerService mAtmService; + private WindowManagerService mWmService; + private TestWindowManagerPolicy mWMPolicy; + private WindowState.PowerManagerWrapper mPowerManagerWrapper; + private InputManagerService mImService; + /** + * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls. + */ + SurfaceControl.Transaction mTransaction; @Override public Statement apply(Statement base, Description description) { @@ -103,8 +130,6 @@ public class SystemServicesTestRule implements TestRule { } private void setUp() { - mMockTracker = new Tracker(); - mMockitoSession = mockitoSession() .spyStatic(LocalServices.class) .mockStatic(LockGuard.class) @@ -112,101 +137,220 @@ public class SystemServicesTestRule implements TestRule { .strictness(Strictness.LENIENT) .startMocking(); + setUpSystemCore(); + setUpLocalServices(); + setUpActivityTaskManagerService(); + setUpWindowManagerService(); + } + + private void setUpSystemCore() { + mHandlerThread = new ServiceThread( + "WmTestsThread", THREAD_PRIORITY_DEFAULT, true /* allowIo */); + mHandlerThread.start(); + doReturn(mock(Watchdog.class)).when(Watchdog::getInstance); - final Context context = getInstrumentation().getTargetContext(); - spyOn(context); + mContext = getInstrumentation().getTargetContext(); + spyOn(mContext); - doReturn(null).when(context) + doReturn(null).when(mContext) .registerReceiver(nullable(BroadcastReceiver.class), any(IntentFilter.class)); - doReturn(null).when(context) + doReturn(null).when(mContext) .registerReceiverAsUser(any(BroadcastReceiver.class), any(UserHandle.class), any(IntentFilter.class), nullable(String.class), nullable(Handler.class)); - final ContentResolver contentResolver = context.getContentResolver(); + final ContentResolver contentResolver = mContext.getContentResolver(); spyOn(contentResolver); doNothing().when(contentResolver) .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class), anyInt()); + } + + private void setUpLocalServices() { + // Tear down any local services just in case. + tearDownLocalServices(); - final AppOpsManager appOpsManager = mock(AppOpsManager.class); - doReturn(appOpsManager).when(context) - .getSystemService(eq(Context.APP_OPS_SERVICE)); + // UriGrantsManagerInternal + final UriGrantsManagerInternal ugmi = mock(UriGrantsManagerInternal.class); + LocalServices.addService(UriGrantsManagerInternal.class, ugmi); + // AppOpsManager + final AppOpsManager aom = mock(AppOpsManager.class); + doReturn(aom).when(mContext).getSystemService(eq(Context.APP_OPS_SERVICE)); + + // DisplayManagerInternal final DisplayManagerInternal dmi = mock(DisplayManagerInternal.class); doReturn(dmi).when(() -> LocalServices.getService(eq(DisplayManagerInternal.class))); + // ColorDisplayServiceInternal + final ColorDisplayService.ColorDisplayServiceInternal cds = + mock(ColorDisplayService.ColorDisplayServiceInternal.class); + doReturn(cds).when(() -> LocalServices.getService( + eq(ColorDisplayService.ColorDisplayServiceInternal.class))); + + final UsageStatsManagerInternal usmi = mock(UsageStatsManagerInternal.class); + LocalServices.addService(UsageStatsManagerInternal.class, usmi); + + // PackageManagerInternal + final PackageManagerInternal packageManagerInternal = mock(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, packageManagerInternal); + doReturn(false).when(packageManagerInternal).isPermissionsReviewRequired( + anyString(), anyInt()); + doReturn(null).when(packageManagerInternal).getDefaultHomeActivity(anyInt()); + + // PowerManagerInternal final PowerManagerInternal pmi = mock(PowerManagerInternal.class); final PowerSaveState state = new PowerSaveState.Builder().build(); doReturn(state).when(pmi).getLowPowerState(anyInt()); doReturn(pmi).when(() -> LocalServices.getService(eq(PowerManagerInternal.class))); - final ActivityManagerInternal ami = mock(ActivityManagerInternal.class); - doReturn(ami).when(() -> LocalServices.getService(eq(ActivityManagerInternal.class))); - - final ActivityTaskManagerInternal atmi = mock(ActivityTaskManagerInternal.class); - doAnswer((InvocationOnMock invocationOnMock) -> { - final Runnable runnable = invocationOnMock.getArgument(0); - if (runnable != null) { - runnable.run(); - } - return null; - }).when(atmi).notifyKeyguardFlagsChanged(nullable(Runnable.class), anyInt()); - doReturn(atmi).when(() -> LocalServices.getService(eq(ActivityTaskManagerInternal.class))); + // PermissionPolicyInternal + final PermissionPolicyInternal ppi = mock(PermissionPolicyInternal.class); + LocalServices.addService(PermissionPolicyInternal.class, ppi); + doReturn(true).when(ppi).checkStartActivity(any(), anyInt(), any()); - final InputManagerService ims = mock(InputManagerService.class); + // InputManagerService + mImService = mock(InputManagerService.class); // InputChannel is final and can't be mocked. final InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM); if (input != null && input.length > 1) { - doReturn(input[1]).when(ims).monitorInput(anyString(), anyInt()); + doReturn(input[1]).when(mImService).monitorInput(anyString(), anyInt()); } - final ActivityTaskManagerService atms = mock(ActivityTaskManagerService.class); - final TaskChangeNotificationController taskChangeNotificationController = mock( - TaskChangeNotificationController.class); - doReturn(taskChangeNotificationController).when(atms).getTaskChangeNotificationController(); - final WindowManagerGlobalLock wmLock = new WindowManagerGlobalLock(); - doReturn(wmLock).when(atms).getGlobalLock(); - - mWindowManagerPolicy = new TestWindowManagerPolicy(this::getWindowManagerService); - mWindowManagerService = WindowManagerService.main( - context, ims, false, false, mWindowManagerPolicy, atms, StubTransaction::new); - - mWindowManagerService.onInitReady(); + // StatusBarManagerInternal + final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class); + doReturn(sbmi).when(() -> LocalServices.getService(eq(StatusBarManagerInternal.class))); + } - final Display display = mWindowManagerService.mDisplayManager.getDisplay(DEFAULT_DISPLAY); - // Display creation is driven by the ActivityManagerService via - // ActivityStackSupervisor. We emulate those steps here. - DisplayContent displayContent = mWindowManagerService.mRoot - .createDisplayContent(display, mock(ActivityDisplay.class)); - displayContent.reconfigureDisplayLocked(); + private void setUpActivityTaskManagerService() { + // ActivityManagerService + mAmService = new ActivityManagerService( + new AMTestInjector(mContext, mHandlerThread), mHandlerThread); + spyOn(mAmService); + doReturn(mock(IPackageManager.class)).when(mAmService).getPackageManager(); + doNothing().when(mAmService).grantEphemeralAccessLocked( + anyInt(), any(), anyInt(), anyInt()); + + // ActivityManagerInternal + final ActivityManagerInternal amInternal = mAmService.mInternal; + spyOn(amInternal); + doNothing().when(amInternal).trimApplications(); + doNothing().when(amInternal).updateCpuStats(); + doNothing().when(amInternal).updateOomAdj(); + doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean()); + doNothing().when(amInternal).updateActivityUsageStats( + any(), anyInt(), anyInt(), any(), any()); + doNothing().when(amInternal).startProcess( + any(), any(), anyBoolean(), anyBoolean(), any(), any()); + doNothing().when(amInternal).updateOomLevelsForDisplay(anyInt()); + LocalServices.addService(ActivityManagerInternal.class, amInternal); + + mAtmService = new TestActivityTaskManagerService(mContext, mAmService); + LocalServices.addService(ActivityTaskManagerInternal.class, mAtmService.getAtmInternal()); + } - mMockTracker.stopTracking(); + private void setUpWindowManagerService() { + mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class); + mWMPolicy = new TestWindowManagerPolicy(this::getWindowManagerService, + mPowerManagerWrapper); + mWmService = WindowManagerService.main( + mContext, mImService, false, false, mWMPolicy, mAtmService, StubTransaction::new); + spyOn(mWmService); + + // Setup factory classes to prevent calls to native code. + mTransaction = spy(StubTransaction.class); + // Return a spied Transaction class than can be used to verify calls. + mWmService.mTransactionFactory = () -> mTransaction; + // Return a SurfaceControl.Builder class that creates mocked SurfaceControl instances. + mWmService.mSurfaceBuilderFactory = (unused) -> new MockSurfaceControlBuilder(); + // Return mocked Surface instances. + mWmService.mSurfaceFactory = () -> mock(Surface.class); + mWmService.mSurfaceAnimationRunner = new SurfaceAnimationRunner( + null, null, mTransaction, mWmService.mPowerManagerInternal); + + mWmService.onInitReady(); + mAmService.setWindowManager(mWmService); + mWmService.mDisplayEnabled = true; + mWmService.mDisplayReady = true; + // Set configuration for default display + mWmService.getDefaultDisplayContentLocked().reconfigureDisplayLocked(); + + // Mock root, some default display, and home stack. + spyOn(mWmService.mRoot); + final ActivityDisplay display = mAtmService.mRootActivityContainer.getDefaultDisplay(); + spyOn(display); + spyOn(display.mDisplayContent); + final ActivityStack homeStack = display.getStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); + spyOn(homeStack); + spyOn(homeStack.mTaskStack); } private void tearDown() { waitUntilWindowManagerHandlersIdle(); - removeLocalServices(); - mWindowManagerService = null; - mWindowManagerPolicy = null; + // Unregister display listener from root to avoid issues with subsequent tests. + mContext.getSystemService(DisplayManager.class) + .unregisterDisplayListener(mAtmService.mRootActivityContainer); + // ProptertiesChangesListener is registered in the constructor of WindowManagerService to + // a static object, so we need to clean it up in tearDown(), even though we didn't set up + // in tests. + DeviceConfig.removeOnPropertiesChangedListener(mWmService.mPropertiesChangedListener); + mWmService = null; + mWMPolicy = null; + mPowerManagerWrapper = null; + + tearDownLocalServices(); + tearDownSystemCore(); + + // Needs to explicitly dispose current static threads because there could be messages + // scheduled at a later time, and all mocks are invalid when it's executed. + DisplayThread.dispose(); + AnimationThread.dispose(); + // Reset priority booster because animation thread has been changed. + WindowManagerService.sThreadPriorityBooster = new WindowManagerThreadPriorityBooster(); + + Mockito.framework().clearInlineMocks(); + } + + private void tearDownSystemCore() { if (mMockitoSession != null) { mMockitoSession.finishMocking(); mMockitoSession = null; } - if (mMockTracker != null) { - mMockTracker.close(); - mMockTracker = null; + if (mHandlerThread != null) { + // Make sure there are no running messages and then quit the thread so the next test + // won't be affected. + mHandlerThread.getThreadHandler().runWithScissors(mHandlerThread::quit, + 0 /* timeout */); } } - private static void removeLocalServices() { + private static void tearDownLocalServices() { + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.removeServiceForTest(PowerManagerInternal.class); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.removeServiceForTest(WindowManagerPolicy.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); + LocalServices.removeServiceForTest(PermissionPolicyInternal.class); + LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); + LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); + LocalServices.removeServiceForTest(StatusBarManagerInternal.class); } WindowManagerService getWindowManagerService() { - return mWindowManagerService; + return mWmService; + } + + ActivityTaskManagerService getActivityTaskManagerService() { + return mAtmService; + } + + WindowState.PowerManagerWrapper getPowerManagerWrapper() { + return mPowerManagerWrapper; } void cleanupWindowManagerHandlers() { @@ -229,6 +373,7 @@ public class SystemServicesTestRule implements TestRule { waitHandlerIdle(wm.mH); waitHandlerIdle(wm.mAnimationHandler); waitHandlerIdle(SurfaceAnimationThread.getHandler()); + waitHandlerIdle(mHandlerThread.getThreadHandler()); } private void waitHandlerIdle(Handler handler) { @@ -251,4 +396,121 @@ public class SystemServicesTestRule implements TestRule { } } } + + protected class TestActivityTaskManagerService extends ActivityTaskManagerService { + // ActivityStackSupervisor may be created more than once while setting up AMS and ATMS. + // We keep the reference in order to prevent creating it twice. + ActivityStackSupervisor mTestStackSupervisor; + + TestActivityTaskManagerService(Context context, ActivityManagerService ams) { + super(context); + spyOn(this); + + mSupportsMultiWindow = true; + mSupportsMultiDisplay = true; + mSupportsSplitScreenMultiWindow = true; + mSupportsFreeformWindowManagement = true; + mSupportsPictureInPicture = true; + + doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); + // allow background activity starts by default + doReturn(true).when(this).isBackgroundActivityStartsEnabled(); + doNothing().when(this).updateCpuStats(); + + // AppOpsService + final AppOpsService aos = mock(AppOpsService.class); + doReturn(aos).when(this).getAppOpsService(); + // Make sure permission checks aren't overridden. + doReturn(AppOpsManager.MODE_DEFAULT) + .when(aos).noteOperation(anyInt(), anyInt(), anyString()); + + setUsageStatsManager(LocalServices.getService(UsageStatsManagerInternal.class)); + ams.mActivityTaskManager = this; + ams.mAtmInternal = mInternal; + onActivityManagerInternalAdded(); + initialize( + ams.mIntentFirewall, ams.mPendingIntentController, mHandlerThread.getLooper()); + spyOn(getLifecycleManager()); + spyOn(getLockTaskController()); + spyOn(getTaskChangeNotificationController()); + initRootActivityContainerMocks(); + } + + void initRootActivityContainerMocks() { + spyOn(mRootActivityContainer); + // Invoked during {@link ActivityStack} creation. + doNothing().when(mRootActivityContainer).updateUIDsPresentOnDisplay(); + // Always keep things awake. + doReturn(true).when(mRootActivityContainer).hasAwakeDisplay(); + // Called when moving activity to pinned stack. + doNothing().when(mRootActivityContainer).ensureActivitiesVisible(any(), anyInt(), + anyBoolean()); + } + + @Override + int handleIncomingUser(int callingPid, int callingUid, int userId, String name) { + return userId; + } + + @Override + protected ActivityStackSupervisor createStackSupervisor() { + if (mTestStackSupervisor == null) { + mTestStackSupervisor = new TestActivityStackSupervisor(this, mH.getLooper()); + } + return mTestStackSupervisor; + } + } + + /** + * An {@link ActivityStackSupervisor} which stubs out certain methods that depend on + * setup not available in the test environment. Also specifies an injector for + */ + protected class TestActivityStackSupervisor extends ActivityStackSupervisor { + + TestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) { + super(service, looper); + spyOn(this); + + // Do not schedule idle that may touch methods outside the scope of the test. + doNothing().when(this).scheduleIdleLocked(); + doNothing().when(this).scheduleIdleTimeoutLocked(any()); + // unit test version does not handle launch wake lock + doNothing().when(this).acquireLaunchWakelock(); + doReturn(mock(KeyguardController.class)).when(this).getKeyguardController(); + + mLaunchingActivityWakeLock = mock(PowerManager.WakeLock.class); + + initialize(); + } + } + + // TODO: Can we just mock this? + private static class AMTestInjector extends ActivityManagerService.Injector { + private ServiceThread mHandlerThread; + + AMTestInjector(Context context, ServiceThread handlerThread) { + super(context); + mHandlerThread = handlerThread; + } + + @Override + public Context getContext() { + return getInstrumentation().getTargetContext(); + } + + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return null; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandlerThread.getThreadHandler(); + } + + @Override + public boolean isNetworkRestrictedForUid(int uid) { + return false; + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 2cebebfc292e..bcff70426c87 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -1277,17 +1277,18 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { } private ActivityRecord createSourceActivity(TestActivityDisplay display) { - final TestActivityStack stack = display.createStack(display.getWindowingMode(), + final ActivityStack stack = display.createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build(); } private void addFreeformTaskTo(TestActivityDisplay display, Rect bounds) { - final TestActivityStack stack = display.createStack(display.getWindowingMode(), + final ActivityStack stack = display.createStack(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); stack.setWindowingMode(WINDOWING_MODE_FREEFORM); final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build(); - task.setBounds(bounds); + // Just work around the unnecessary adjustments for bounds. + task.getWindowConfiguration().setBounds(bounds); } private void assertEquivalentWindowingMode(int expected, int actual, int parentWindowingMode) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index a0302f69029b..c83401b2eb65 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -28,14 +28,19 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_90; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; +import static com.android.server.wm.DisplayRotation.FIXED_TO_USER_ROTATION_ENABLED; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertEquals; @@ -64,6 +69,7 @@ import android.util.DisplayMetrics; import android.util.Xml; import android.view.DisplayInfo; +import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import com.android.internal.app.IVoiceInteractor; @@ -102,6 +108,7 @@ public class TaskRecordTests extends ActivityTestsBase { public void setUp() throws Exception { TaskRecord.setTaskRecordFactory(null); mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/); + removeGlobalMinSizeRestriction(); } @Test @@ -165,6 +172,7 @@ public class TaskRecordTests extends ActivityTestsBase { /** Ensures that bounds on freeform stacks are not clipped. */ @Test + @FlakyTest(bugId = 137879065) public void testAppBounds_FreeFormBounds() { final Rect freeFormBounds = new Rect(mParentBounds); freeFormBounds.offset(10, 10); @@ -174,6 +182,7 @@ public class TaskRecordTests extends ActivityTestsBase { /** Ensures that fully contained bounds are not clipped. */ @Test + @FlakyTest(bugId = 137879065) public void testAppBounds_ContainedBounds() { final Rect insetBounds = new Rect(mParentBounds); insetBounds.inset(5, 5, 5, 5); @@ -182,6 +191,7 @@ public class TaskRecordTests extends ActivityTestsBase { } @Test + @FlakyTest(bugId = 137879065) public void testFitWithinBounds() { final Rect parentBounds = new Rect(10, 10, 200, 200); ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); @@ -221,6 +231,7 @@ public class TaskRecordTests extends ActivityTestsBase { /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */ @Test + @FlakyTest(bugId = 137879065) public void testBoundsOnModeChangeFreeformToFullscreen() { ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display) @@ -248,18 +259,6 @@ public class TaskRecordTests extends ActivityTestsBase { } /** - * This is a temporary hack to trigger an onConfigurationChange at the task level after an - * orientation is requested. Normally this is done by the onDescendentOrientationChanged call - * up the WM hierarchy, but since the WM hierarchy is mocked out, it doesn't happen here. - * TODO: remove this when we either get a WM hierarchy or when hierarchies are merged. - */ - private void setActivityRequestedOrientation(ActivityRecord activity, int orientation) { - activity.setRequestedOrientation(orientation); - ConfigurationContainer taskRecord = activity.getParent(); - taskRecord.onConfigurationChanged(taskRecord.getParent().getConfiguration()); - } - - /** * Tests that a task with forced orientation has orientation-consistent bounds within the * parent. */ @@ -268,49 +267,48 @@ public class TaskRecordTests extends ActivityTestsBase { final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); DisplayInfo info = new DisplayInfo(); + mService.mContext.getDisplay().getDisplayInfo(info); info.logicalWidth = fullScreenBounds.width(); info.logicalHeight = fullScreenBounds.height(); ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP); assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null); - // Override display orientation. Normally this is available via DisplayContent, but DC - // is mocked-out. - display.getRequestedOverrideConfiguration().orientation = - Configuration.ORIENTATION_LANDSCAPE; - display.onRequestedOverrideConfigurationChanged( - display.getRequestedOverrideConfiguration()); + // Fix the display orientation to landscape which is the natural rotation (0) for the test + // display. + final DisplayRotation dr = display.mDisplayContent.getDisplayRotation(); + dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED); + dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0); + ActivityStack stack = new StackBuilder(mRootActivityContainer) .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); TaskRecord task = stack.getChildAt(0); ActivityRecord root = task.getTopActivity(); - assertEquals(root, task.getTopActivity()); assertEquals(fullScreenBounds, task.getBounds()); // Setting app to fixed portrait fits within parent - setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT); + root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertEquals(root, task.getRootActivity()); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); - assertTrue(task.getBounds().width() < task.getBounds().height()); + assertThat(task.getBounds().width()).isLessThan(task.getBounds().height()); assertEquals(fullScreenBounds.height(), task.getBounds().height()); // Top activity gets used ActivityRecord top = new ActivityBuilder(mService).setTask(task).setStack(stack).build(); assertEquals(top, task.getTopActivity()); - setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE); - assertTrue(task.getBounds().width() > task.getBounds().height()); + top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); assertEquals(task.getBounds().width(), fullScreenBounds.width()); // Setting app to unspecified restores - setActivityRequestedOrientation(top, SCREEN_ORIENTATION_UNSPECIFIED); + top.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); assertEquals(fullScreenBounds, task.getBounds()); // Setting app to fixed landscape and changing display - setActivityRequestedOrientation(top, SCREEN_ORIENTATION_LANDSCAPE); - // simulate display orientation changing (normally done via DisplayContent) - display.getRequestedOverrideConfiguration().orientation = - Configuration.ORIENTATION_PORTRAIT; - display.setBounds(fullScreenBoundsPort); - assertTrue(task.getBounds().width() > task.getBounds().height()); + top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + // Fix the display orientation to portrait which is 90 degrees for the test display. + dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90); + + assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); // in FREEFORM, no constraint @@ -323,7 +321,7 @@ public class TaskRecordTests extends ActivityTestsBase { // FULLSCREEN letterboxes bounds stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertTrue(task.getBounds().width() > task.getBounds().height()); + assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); // FREEFORM restores bounds as before @@ -335,6 +333,7 @@ public class TaskRecordTests extends ActivityTestsBase { public void testIgnoresForcedOrientationWhenParentHandles() { final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); DisplayInfo info = new DisplayInfo(); + mService.mContext.getDisplay().getDisplayInfo(info); info.logicalWidth = fullScreenBounds.width(); info.logicalHeight = fullScreenBounds.height(); ActivityDisplay display = addNewActivityDisplayAt(info, POSITION_TOP); @@ -355,7 +354,7 @@ public class TaskRecordTests extends ActivityTestsBase { // Setting app to fixed portrait fits within parent, but TaskRecord shouldn't adjust the // bounds because its parent says it will handle it at a later time. - setActivityRequestedOrientation(root, SCREEN_ORIENTATION_PORTRAIT); + root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertEquals(root, task.getRootActivity()); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); assertEquals(fullScreenBounds, task.getBounds()); @@ -646,7 +645,7 @@ public class TaskRecordTests extends ActivityTestsBase { final ActivityRecord activity0 = task0.getChildAt(0); final TaskRecord task1 = getTestTask(); - final ActivityRecord activity1 = task0.getChildAt(0); + final ActivityRecord activity1 = task1.getChildAt(0); assertEquals(task0.taskId, ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */)); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java new file mode 100644 index 000000000000..c14396933143 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import android.hardware.display.DisplayManagerGlobal; +import android.view.Display; +import android.view.DisplayInfo; + +class TestActivityDisplay extends ActivityDisplay { + private final ActivityStackSupervisor mSupervisor; + + static TestActivityDisplay create(ActivityStackSupervisor supervisor) { + return create(supervisor, SystemServicesTestRule.sNextDisplayId++); + } + + static TestActivityDisplay create(ActivityStackSupervisor supervisor, DisplayInfo info) { + return create(supervisor, SystemServicesTestRule.sNextDisplayId++, info); + } + + static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId) { + final DisplayInfo info = new DisplayInfo(); + supervisor.mService.mContext.getDisplay().getDisplayInfo(info); + return create(supervisor, displayId, info); + } + + static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId, + DisplayInfo info) { + if (displayId == DEFAULT_DISPLAY) { + synchronized (supervisor.mService.mGlobalLock) { + return new TestActivityDisplay(supervisor, + supervisor.mRootActivityContainer.mDisplayManager.getDisplay(displayId)); + } + } + final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, + info, DEFAULT_DISPLAY_ADJUSTMENTS); + + synchronized (supervisor.mService.mGlobalLock) { + return new TestActivityDisplay(supervisor, display); + } + } + + private TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) { + super(supervisor.mService.mRootActivityContainer, display); + // Normally this comes from display-properties as exposed by WM. Without that, just + // hard-code to FULLSCREEN for tests. + setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mSupervisor = supervisor; + spyOn(this); + spyOn(mDisplayContent); + doAnswer(invocation -> { + // Bypass all the rotation animation and display freezing stuff for testing and just + // set the rotation we want for the display + final DisplayContent dc = mDisplayContent; + final int oldRotation = dc.getRotation(); + final int rotation = dc.getDisplayRotation().rotationForOrientation( + dc.getLastOrientation(), oldRotation); + if (oldRotation == rotation) { + return false; + } + dc.setLayoutNeeded(); + dc.setRotation(rotation); + return true; + }).when(mDisplayContent).updateRotationUnchecked(anyBoolean()); + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + @Override + ActivityStack createStackUnchecked(int windowingMode, int activityType, + int stackId, boolean onTop) { + return new ActivityTestsBase.StackBuilder(mSupervisor.mRootActivityContainer) + .setDisplay(this) + .setWindowingMode(windowingMode) + .setActivityType(activityType) + .setStackId(stackId) + .setOnTop(onTop) + .setCreateActivity(false) + .build(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 2e5ce69a8564..bb89446dd6e6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -40,12 +40,14 @@ import android.view.animation.Animation; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.WindowState.PowerManagerWrapper; import java.io.PrintWriter; import java.util.function.Supplier; class TestWindowManagerPolicy implements WindowManagerPolicy { private final Supplier<WindowManagerService> mWmSupplier; + private final PowerManagerWrapper mPowerManagerWrapper; int mRotationToReport = 0; boolean mKeyguardShowingAndNotOccluded = false; @@ -53,8 +55,10 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { private Runnable mRunnableWhenAddingSplashScreen; - TestWindowManagerPolicy(Supplier<WindowManagerService> wmSupplier) { + TestWindowManagerPolicy(Supplier<WindowManagerService> wmSupplier, + PowerManagerWrapper powerManagerWrapper) { mWmSupplier = wmSupplier; + mPowerManagerWrapper = powerManagerWrapper; } @Override @@ -121,7 +125,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { doReturn(mock(IBinder.class)).when(iWindow).asBinder(); window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken, "Starting window", 0 /* ownerId */, false /* internalWindows */, wm, - mock(Session.class), iWindow); + mock(Session.class), iWindow, mPowerManagerWrapper); atoken.startingWindow = window; } if (mRunnableWhenAddingSplashScreen != null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index acfc2ea3d402..921f105d1d25 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -27,6 +27,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -113,6 +114,7 @@ public class WindowContainerTests extends WindowTestsBase { @Test public void testAddChildSetsSurfacePosition() { + reset(mTransaction); try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) { WindowContainer child = new WindowContainer(mWm); child.setBounds(1, 1, 10, 10); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java index 06bcdf8fcadf..60cefe858c54 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java @@ -57,6 +57,7 @@ public class WindowFrameTests extends WindowTestsBase { private final IWindow mIWindow = new TestIWindow(); private final Rect mEmptyRect = new Rect(); + private DisplayContent mTestDisplayContent; static class FrameTestWindowState extends WindowState { boolean mDockedResizingForTest = false; @@ -77,6 +78,9 @@ public class WindowFrameTests extends WindowTestsBase { @Before public void setUp() throws Exception { mStubStack = mock(TaskStack.class); + DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo); + testDisplayInfo.displayCutout = null; + mTestDisplayContent = createNewDisplay(testDisplayInfo); } // Do not use this function directly in the tests below. Instead, use more explicit function @@ -100,6 +104,10 @@ public class WindowFrameTests extends WindowTestsBase { assertRect(w.getStableInsets(), left, top, right, bottom); } + private void assertFrame(WindowState w, Rect frame) { + assertEquals(w.getFrameLw(), frame); + } + private void assertFrame(WindowState w, int left, int top, int right, int bottom) { assertRect(w.getFrameLw(), left, top, right, bottom); } @@ -380,9 +388,10 @@ public class WindowFrameTests extends WindowTestsBase { } @Test + @FlakyTest(bugId = 137879065) public void testLayoutLetterboxedWindow() { // First verify task behavior in multi-window mode. - final DisplayInfo displayInfo = mWm.getDefaultDisplayContentLocked().getDisplayInfo(); + final DisplayInfo displayInfo = mTestDisplayContent.getDisplayInfo(); final int logicalWidth = displayInfo.logicalWidth; final int logicalHeight = displayInfo.logicalHeight; @@ -413,13 +422,14 @@ public class WindowFrameTests extends WindowTestsBase { final Rect cf = new Rect(xInset, 0, logicalWidth - xInset, logicalHeight); Configuration config = new Configuration(w.mAppToken.getRequestedOverrideConfiguration()); config.windowConfiguration.setBounds(cf); + config.windowConfiguration.setAppBounds(cf); w.mAppToken.onRequestedOverrideConfigurationChanged(config); pf.set(0, 0, logicalWidth, logicalHeight); task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); task.setBounds(null); windowFrames.setFrames(pf, pf, pf, cf, cf, pf, cf, mEmptyRect); w.computeFrameLw(); - assertFrame(w, cf.left, cf.top, cf.right, cf.bottom); + assertFrame(w, cf); assertContentFrame(w, cf); assertContentInset(w, 0, 0, 0, 0); } @@ -483,7 +493,7 @@ public class WindowFrameTests extends WindowTestsBase { w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; task.setWindowingMode(WINDOWING_MODE_FREEFORM); - DisplayContent dc = mWm.getDefaultDisplayContentLocked(); + DisplayContent dc = mTestDisplayContent; dc.mInputMethodTarget = w; WindowState mockIme = mock(WindowState.class); Mockito.doReturn(true).when(mockIme).isVisibleNow(); @@ -537,7 +547,7 @@ public class WindowFrameTests extends WindowTestsBase { attrs.width = width; attrs.height = height; - AppWindowToken token = createAppWindowToken(mWm.getDefaultDisplayContentLocked(), + AppWindowToken token = createAppWindowToken(mTestDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); FrameTestWindowState ws = new FrameTestWindowState(mWm, mIWindow, token, attrs); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 36698eae274c..b73162834a52 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -346,24 +346,28 @@ public class WindowStateTests extends WindowTestsBase { firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - reset(sPowerManagerWrapper); + final WindowState.PowerManagerWrapper powerManagerWrapper = + mSystemServicesTestRule.getPowerManagerWrapper(); + reset(powerManagerWrapper); firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(sPowerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); - reset(sPowerManagerWrapper); + reset(powerManagerWrapper); secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(sPowerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); } private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow, boolean expectedWakeupCalled, boolean expectedCurrentLaunchCanTurnScreenOn) { - reset(sPowerManagerWrapper); + final WindowState.PowerManagerWrapper powerManagerWrapper = + mSystemServicesTestRule.getPowerManagerWrapper(); + reset(powerManagerWrapper); appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */); if (expectedWakeupCalled) { - verify(sPowerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString()); } else { - verify(sPowerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString()); + verify(powerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString()); } // If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false // because the state will be consumed. @@ -517,13 +521,19 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testGetTransformationMatrix() { + final int PARENT_WINDOW_OFFSET = 1; + final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2; + final int WINDOW_OFFSET = 3; + final float OFFSET_SUM = + PARENT_WINDOW_OFFSET + DISPLAY_IN_PARENT_WINDOW_OFFSET + WINDOW_OFFSET; + synchronized (mWm.mGlobalLock) { final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0"); - win0.getFrameLw().offsetTo(1, 0); final DisplayContent dc = createNewDisplay(); + win0.getFrameLw().offsetTo(PARENT_WINDOW_OFFSET, 0); dc.reparentDisplayContent(win0, win0.getSurfaceControl()); - dc.updateLocation(win0, 2, 0); + dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0); final float[] values = new float[9]; final Matrix matrix = new Matrix(); @@ -531,12 +541,12 @@ public class WindowStateTests extends WindowTestsBase { final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1"); win1.mHasSurface = true; win1.mSurfaceControl = mock(SurfaceControl.class); - win1.getFrameLw().offsetTo(3, 0); + win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0); win1.updateSurfacePosition(t); win1.getTransformationMatrix(values, matrix); matrix.getValues(values); - assertEquals(6f, values[Matrix.MTRANS_X], 0f); + assertEquals(OFFSET_SUM, values[Matrix.MTRANS_X], 0f); assertEquals(0f, values[Matrix.MTRANS_Y], 0f); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index dc461d149cee..d1cf1c3e2f01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -20,8 +20,6 @@ import static android.app.AppOpsManager.OP_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.os.Process.SYSTEM_UID; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; @@ -38,33 +36,27 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.server.wm.ActivityDisplay.POSITION_TOP; import static org.mockito.Mockito.mock; - import android.content.Context; import android.content.res.Configuration; -import android.hardware.display.DisplayManagerGlobal; import android.testing.DexmakerShareClassLoaderRule; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; import android.view.IWindow; -import android.view.Surface; import android.view.SurfaceControl.Transaction; import android.view.WindowManager; import com.android.server.AttributeCache; -import com.android.server.wm.utils.MockTracker; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; -import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; @@ -79,10 +71,6 @@ class WindowTestsBase { WindowManagerService mWm; private final IWindow mIWindow = new TestIWindow(); private Session mMockSession; - // The default display is removed in {@link #setUp} and then we iterate over all displays to - // make sure we don't collide with any existing display. If we run into no other display, the - // added display should be treated as default. This cannot be the default display - private static int sNextDisplayId = DEFAULT_DISPLAY + 1; static int sNextStackId = 1000; /** Non-default display. */ @@ -99,8 +87,6 @@ class WindowTestsBase { WindowState mChildAppWindowBelow; HashSet<WindowState> mCommonWindows; - private MockTracker mMockTracker; - /** * Spied {@link Transaction} class than can be used to verify calls. */ @@ -112,49 +98,27 @@ class WindowTestsBase { @Rule public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(); - static WindowState.PowerManagerWrapper sPowerManagerWrapper; - @BeforeClass public static void setUpOnceBase() { AttributeCache.init(getInstrumentation().getTargetContext()); - - sPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class); - } - - @AfterClass - public static void tearDownOnceBase() throws IOException { - sPowerManagerWrapper = null; } @Before public void setUpBase() { - mMockTracker = new MockTracker(); - // If @Before throws an exception, the error isn't logged. This will make sure any failures // in the set up are clear. This can be removed when b/37850063 is fixed. try { mMockSession = mock(Session.class); - mTransaction = spy(StubTransaction.class); final Context context = getInstrumentation().getTargetContext(); mWm = mSystemServicesTestRule.getWindowManagerService(); - - // Setup factory classes to prevent calls to native code. - - // Return a spied Transaction class than can be used to verify calls. - mWm.mTransactionFactory = () -> mTransaction; - // Return a SurfaceControl.Builder class that creates mocked SurfaceControl instances. - mWm.mSurfaceBuilderFactory = (unused) -> new MockSurfaceControlBuilder(); - // Return mocked Surface instances. - mWm.mSurfaceFactory = () -> mock(Surface.class); + mTransaction = mSystemServicesTestRule.mTransaction; beforeCreateDisplay(); context.getDisplay().getDisplayInfo(mDisplayInfo); mDisplayContent = createNewDisplay(); - mWm.mDisplayEnabled = true; - mWm.mDisplayReady = true; // Set-up some common windows. mCommonWindows = new HashSet<>(); @@ -211,12 +175,6 @@ class WindowTestsBase { nonCommonWindows.pollLast().removeImmediately(); } - for (int i = mWm.mRoot.mChildren.size() - 1; i >= 0; --i) { - final DisplayContent displayContent = mWm.mRoot.mChildren.get(i); - if (!displayContent.isDefaultDisplay) { - displayContent.removeImmediately(); - } - } // Remove app transition & window freeze timeout callbacks to prevent unnecessary // actions after test. mWm.getDefaultDisplayContentLocked().mAppTransition @@ -230,11 +188,7 @@ class WindowTestsBase { } catch (Exception e) { Log.e(TAG, "Failed to tear down test", e); throw e; - } finally { - mMockTracker.close(); - mMockTracker = null; } - } private WindowState createCommonWindow(WindowState parent, int type, String name) { @@ -356,12 +310,13 @@ class WindowTestsBase { WindowState createWindow(WindowState parent, int type, WindowToken token, String name, int ownerId, boolean ownerCanAddInternalSystemWindow) { return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow, - mWm, mMockSession, mIWindow); + mWm, mMockSession, mIWindow, mSystemServicesTestRule.getPowerManagerWrapper()); } static WindowState createWindow(WindowState parent, int type, WindowToken token, String name, int ownerId, boolean ownerCanAddInternalSystemWindow, - WindowManagerService service, Session session, IWindow iWindow) { + WindowManagerService service, Session session, IWindow iWindow, + WindowState.PowerManagerWrapper powerManagerWrapper) { synchronized (service.mGlobalLock) { final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); attrs.setTitle(name); @@ -369,7 +324,7 @@ class WindowTestsBase { final WindowState w = new WindowState(service, session, iWindow, token, parent, OP_NONE, 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow, - sPowerManagerWrapper); + powerManagerWrapper); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with // adding it to the token... token.addWindow(w); @@ -408,13 +363,11 @@ class WindowTestsBase { } /** Creates a {@link DisplayContent} and adds it to the system. */ - DisplayContent createNewDisplay(DisplayInfo displayInfo) { - final int displayId = sNextDisplayId++; - final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, - displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - synchronized (mWm.mGlobalLock) { - return new DisplayContent(display, mWm, mock(ActivityDisplay.class)); - } + DisplayContent createNewDisplay(DisplayInfo info) { + final ActivityDisplay display = + TestActivityDisplay.create(mWm.mAtmService.mStackSupervisor, info); + mWm.mAtmService.mRootActivityContainer.addChild(display, POSITION_TOP); + return display.mDisplayContent; } /** @@ -428,17 +381,7 @@ class WindowTestsBase { DisplayInfo displayInfo = new DisplayInfo(); displayInfo.copyFrom(mDisplayInfo); displayInfo.state = displayState; - final int displayId = sNextDisplayId++; - final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, - displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - synchronized (mWm.mGlobalLock) { - // Display creation is driven by DisplayWindowController via ActivityStackSupervisor. - // We skip those steps here. - final ActivityDisplay mockAd = mock(ActivityDisplay.class); - final DisplayContent displayContent = mWm.mRoot.createDisplayContent(display, mockAd); - displayContent.reconfigureDisplayLocked(); - return displayContent; - } + return createNewDisplay(displayInfo); } /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */ diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java index a6e675ab5f7e..7f0948288799 100644 --- a/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java +++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockTracker.java @@ -89,7 +89,7 @@ public class MockTracker implements MockCreationListener, AutoCloseable { for (final Object mock : mMocks.keySet()) { if (MockUtil.isMock(mock)) { - Mockito.reset(mock); + mMockitoFramework.clearInlineMock(mock); } } mMocks.clear(); diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 43fb304e8fb6..42a5501cc229 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -692,7 +692,7 @@ public final class Telephony { } /** - * Contains all sent text-based SMS messages in the SMS app. + * Contains all draft text-based SMS messages in the SMS app. */ public static final class Draft implements BaseColumns, TextBasedSmsColumns { @@ -808,7 +808,15 @@ public final class Telephony { } /** - * Contains all sent text-based SMS messages in the SMS app. + * Contains a view of SMS conversations (also referred to as threads). This is similar to + * {@link Threads}, but only includes SMS messages and columns relevant to SMS + * conversations. + * <p> + * Note that this view ignores any information about MMS messages, it is a + * view of conversations as if MMS messages did not exist at all. This means that all + * relevant information, such as snippets and message count, will ignore any MMS messages + * that might be in the same thread through other views and present only data based on the + * SMS messages in that thread. */ public static final class Conversations implements BaseColumns, TextBasedSmsColumns { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 60de214cf878..10d4b8dbf33e 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2656,25 +2656,32 @@ public class CarrierConfigManager { "call_waiting_service_class_int"; /** - * This configuration allow the system UI to display different 5G icon for different 5G status. + * This configuration allow the system UI to display different 5G icon for different 5G + * scenario. * - * There are four 5G status: + * There are five 5G scenarios: * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using * millimeter wave. * 2. connected: device currently connected to 5G cell as the secondary cell but not using * millimeter wave. - * 3. not_restricted: device camped on a network that has 5G capability(not necessary to connect - * a 5G cell as a secondary cell) and the use of 5G is not restricted. - * 4. restricted: device camped on a network that has 5G capability(not necessary to connect a + * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability(not necessary + * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC + * currently in IDLE state. + * 4. not_restricted_rrc_con: device camped on a network that has 5G capability(not necessary + * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC + * currently in CONNECTED state. + * 5. restricted: device camped on a network that has 5G capability(not necessary to connect a * 5G cell as a secondary cell) but the use of 5G is restricted. * * The configured string contains multiple key-value pairs separated by comma. For each pair, * the key and value is separated by a colon. The key is corresponded to a 5G status above and * the value is the icon name. Use "None" as the icon name if no icon should be shown in a - * specific 5G status. + * specific 5G scenario. If the scenario is "None", config can skip this key and value. * - * Here is an example of the configuration: - * "connected_mmwave:5GPlus,connected:5G,not_restricted:None,restricted:None" + * Here is an example: + * UE want to display 5GPlus icon for scenario#1, and 5G icon for scenario#2; otherwise no + * define. + * The configuration is: "connected_mmwave:5GPlus,connected:5G" * * @hide */ @@ -3131,6 +3138,13 @@ public class CarrierConfigManager { public static final String KEY_SUPPORT_WPS_OVER_IMS_BOOL = "support_wps_over_ims_bool"; + /** + * Holds the list of carrier certificate hashes. Note that each carrier has its own certificates + * @hide + */ + public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = + "carrier_certificate_string_array"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -3509,7 +3523,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_USE_CALLER_ID_USSD_BOOL, false); sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */); sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING, - "connected_mmwave:None,connected:5G,not_restricted:None,restricted:None"); + "connected_mmwave:5G,connected:5G"); sDefaults.putInt(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 0); sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */ @@ -3552,6 +3566,7 @@ public class CarrierConfigManager { }); sDefaults.putBoolean(KEY_SUPPORT_WPS_OVER_IMS_BOOL, true); sDefaults.putAll(Ims.getDefaults()); + sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, null); } /** diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java index 258a873c3ac5..432978d1c866 100644 --- a/telephony/java/android/telephony/CellIdentity.java +++ b/telephony/java/android/telephony/CellIdentity.java @@ -17,6 +17,7 @@ package android.telephony; import android.annotation.CallSuper; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -61,7 +62,7 @@ public abstract class CellIdentity implements Parcelable { mType = type; // Only allow INT_MAX if unknown string mcc/mnc - if (mcc == null || mcc.matches("^[0-9]{3}$")) { + if (mcc == null || isMcc(mcc)) { mMccStr = mcc; } else if (mcc.isEmpty() || mcc.equals(String.valueOf(Integer.MAX_VALUE))) { // If the mccStr is empty or unknown, set it as null. @@ -73,7 +74,7 @@ public abstract class CellIdentity implements Parcelable { log("invalid MCC format: " + mcc); } - if (mnc == null || mnc.matches("^[0-9]{2,3}$")) { + if (mnc == null || isMnc(mnc)) { mMncStr = mnc; } else if (mnc.isEmpty() || mnc.equals(String.valueOf(Integer.MAX_VALUE))) { // If the mncStr is empty or unknown, set it as null. @@ -262,4 +263,30 @@ public abstract class CellIdentity implements Parcelable { if ((value < rangeMin || value > rangeMax) && value != special) return CellInfo.UNAVAILABLE; return value; } + + /** @hide */ + private static boolean isMcc(@NonNull String mcc) { + // ensure no out of bounds indexing + if (mcc.length() != 3) return false; + + // Character.isDigit allows all unicode digits, not just [0-9] + for (int i = 0; i < 3; i++) { + if (mcc.charAt(i) < '0' || mcc.charAt(i) > '9') return false; + } + + return true; + } + + /** @hide */ + private static boolean isMnc(@NonNull String mnc) { + // ensure no out of bounds indexing + if (mnc.length() < 2 || mnc.length() > 3) return false; + + // Character.isDigit allows all unicode digits, not just [0-9] + for (int i = 0; i < mnc.length(); i++) { + if (mnc.charAt(i) < '0' || mnc.charAt(i) > '9') return false; + } + + return true; + } } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index a8491d3f4d04..36e81232100c 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -38,6 +38,7 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -147,7 +148,14 @@ public class SubscriptionInfo implements Parcelable { * The access rules for this subscription, if it is embedded and defines any. */ @Nullable - private UiccAccessRule[] mAccessRules; + private UiccAccessRule[] mNativeAccessRules; + + /** + * The carrier certificates for this subscription that are saved in carrier configs. + * The other carrier certificates are embedded on Uicc and stored as part of mNativeAccessRules. + */ + @Nullable + private UiccAccessRule[] mCarrierConfigAccessRules; /** * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the @@ -206,12 +214,12 @@ public class SubscriptionInfo implements Parcelable { public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, - @Nullable UiccAccessRule[] accessRules, String cardString) { + @Nullable UiccAccessRule[] nativeAccessRules, String cardString) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, - roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, + roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1, false, null, false, TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null); + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null); } /** @@ -220,12 +228,12 @@ public class SubscriptionInfo implements Parcelable { public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, - @Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic, - @Nullable String groupUUID, int carrierId, int profileClass) { + @Nullable UiccAccessRule[] nativeAccessRules, String cardString, + boolean isOpportunistic, @Nullable String groupUUID, int carrierId, int profileClass) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, - roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, + roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1, isOpportunistic, groupUUID, false, carrierId, profileClass, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null); + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null); } /** @@ -234,9 +242,10 @@ public class SubscriptionInfo implements Parcelable { public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, - @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, + @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId, boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled, - int carrierId, int profileClass, int subType, @Nullable String groupOwner) { + int carrierId, int profileClass, int subType, @Nullable String groupOwner, + @Nullable UiccAccessRule[] carrierConfigAccessRules) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -251,7 +260,7 @@ public class SubscriptionInfo implements Parcelable { this.mMnc = mnc; this.mCountryIso = countryIso; this.mIsEmbedded = isEmbedded; - this.mAccessRules = accessRules; + this.mNativeAccessRules = nativeAccessRules; this.mCardString = cardString; this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; @@ -261,6 +270,7 @@ public class SubscriptionInfo implements Parcelable { this.mProfileClass = profileClass; this.mSubscriptionType = subType; this.mGroupOwner = groupOwner; + this.mCarrierConfigAccessRules = carrierConfigAccessRules; } /** @@ -566,7 +576,8 @@ public class SubscriptionInfo implements Parcelable { if (!isEmbedded()) { throw new UnsupportedOperationException("Not an embedded subscription"); } - if (mAccessRules == null) { + List<UiccAccessRule> allAccessRules = getAllAccessRules(); + if (allAccessRules == null) { return false; } PackageManager packageManager = context.getPackageManager(); @@ -576,7 +587,7 @@ public class SubscriptionInfo implements Parcelable { } catch (PackageManager.NameNotFoundException e) { throw new IllegalArgumentException("Unknown package: " + packageName, e); } - for (UiccAccessRule rule : mAccessRules) { + for (UiccAccessRule rule : allAccessRules) { if (rule.getCarrierPrivilegeStatus(packageInfo) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return true; @@ -586,7 +597,10 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the {@link UiccAccessRule}s dictating who is authorized to manage this subscription. + * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who + * is authorized to manage this subscription. + * TODO and fix it properly in R / master: either deprecate this and have 3 APIs + * native + carrier + all, or have this return all by default. * @throws UnsupportedOperationException if this subscription is not embedded. * @hide */ @@ -595,8 +609,25 @@ public class SubscriptionInfo implements Parcelable { if (!isEmbedded()) { throw new UnsupportedOperationException("Not an embedded subscription"); } - if (mAccessRules == null) return null; - return Arrays.asList(mAccessRules); + if (mNativeAccessRules == null) return null; + return Arrays.asList(mNativeAccessRules); + } + + /** + * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs + * dictating who is authorized to manage this subscription. + * @hide + */ + public @Nullable List<UiccAccessRule> getAllAccessRules() { + if (!isEmbedded()) { + throw new UnsupportedOperationException("Not an embedded subscription"); + } + List<UiccAccessRule> merged = new ArrayList<>(); + if (mNativeAccessRules != null) merged.addAll(getAccessRules()); + if (mCarrierConfigAccessRules != null) { + merged.addAll(Arrays.asList(mCarrierConfigAccessRules)); + } + return merged.isEmpty() ? null : merged; } /** @@ -651,7 +682,7 @@ public class SubscriptionInfo implements Parcelable { String countryIso = source.readString(); Bitmap iconBitmap = source.readParcelable(Bitmap.class.getClassLoader()); boolean isEmbedded = source.readBoolean(); - UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR); + UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR); String cardString = source.readString(); int cardId = source.readInt(); boolean isOpportunistic = source.readBoolean(); @@ -663,11 +694,14 @@ public class SubscriptionInfo implements Parcelable { String[] ehplmns = source.readStringArray(); String[] hplmns = source.readStringArray(); String groupOwner = source.readString(); + UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray( + UiccAccessRule.CREATOR); SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, - countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic, - groupUUID, isGroupDisabled, carrierid, profileClass, subType, groupOwner); + countryIso, isEmbedded, nativeAccessRules, cardString, cardId, isOpportunistic, + groupUUID, isGroupDisabled, carrierid, profileClass, subType, groupOwner, + carrierConfigAccessRules); info.setAssociatedPlmns(ehplmns, hplmns); return info; } @@ -694,7 +728,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeString(mCountryIso); dest.writeParcelable(mIconBitmap, flags); dest.writeBoolean(mIsEmbedded); - dest.writeTypedArray(mAccessRules, flags); + dest.writeTypedArray(mNativeAccessRules, flags); dest.writeString(mCardString); dest.writeInt(mCardId); dest.writeBoolean(mIsOpportunistic); @@ -706,6 +740,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeStringArray(mEhplmns); dest.writeStringArray(mHplmns); dest.writeString(mGroupOwner); + dest.writeTypedArray(mCarrierConfigAccessRules, flags); } @Override @@ -736,9 +771,9 @@ public class SubscriptionInfo implements Parcelable { + " carrierId=" + mCarrierId + " displayName=" + mDisplayName + " carrierName=" + mCarrierName + " nameSource=" + mNameSource + " iconTint=" + mIconTint + " mNumber=" + Rlog.pii(Build.IS_DEBUGGABLE, mNumber) - + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc=" + mMcc - + " mnc=" + mMnc + " mCountryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded - + " accessRules=" + Arrays.toString(mAccessRules) + + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc + + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded + + " nativeAccessRules " + Arrays.toString(mNativeAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " isOpportunistic=" + mIsOpportunistic + " mGroupUUID=" + mGroupUUID + " mIsGroupDisabled=" + mIsGroupDisabled @@ -746,14 +781,15 @@ public class SubscriptionInfo implements Parcelable { + " ehplmns=" + Arrays.toString(mEhplmns) + " hplmns=" + Arrays.toString(mHplmns) + " subscriptionType=" + mSubscriptionType - + " mGroupOwner=" + mGroupOwner + "}"; + + " mGroupOwner=" + mGroupOwner + + " carrierConfigAccessRules=" + mCarrierConfigAccessRules + "}"; } @Override public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, - mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules, + mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner); } @@ -789,7 +825,7 @@ public class SubscriptionInfo implements Parcelable { && Objects.equals(mGroupOwner, toCompare.mGroupOwner) && TextUtils.equals(mDisplayName, toCompare.mDisplayName) && TextUtils.equals(mCarrierName, toCompare.mCarrierName) - && Arrays.equals(mAccessRules, toCompare.mAccessRules) + && Arrays.equals(mNativeAccessRules, toCompare.mNativeAccessRules) && mProfileClass == toCompare.mProfileClass && Arrays.equals(mEhplmns, toCompare.mEhplmns) && Arrays.equals(mHplmns, toCompare.mHplmns); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 8c53c5125b8d..a84c916dde34 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -571,6 +571,16 @@ public class SubscriptionManager { public static final String ACCESS_RULES = "access_rules"; /** + * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from + * {@link UiccAccessRule#encodeRules} but for the rules that come from CarrierConfigs. + * Only present if there are access rules in CarrierConfigs + * <p>TYPE: BLOB + * @hide + */ + public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS = + "access_rules_from_carrier_configs"; + + /** * TelephonyProvider column name identifying whether an embedded subscription is on a removable * card. Such subscriptions are marked inaccessible as soon as the current card is removed. * Otherwise, they will remain accessible unless explicitly deleted. Only present if @@ -2601,7 +2611,7 @@ public class SubscriptionManager { if (!info.isEmbedded()) { throw new IllegalArgumentException("Not an embedded subscription"); } - if (info.getAccessRules() == null) { + if (info.getAllAccessRules() == null) { return false; } PackageManager packageManager = mContext.getPackageManager(); @@ -2611,7 +2621,7 @@ public class SubscriptionManager { } catch (PackageManager.NameNotFoundException e) { throw new IllegalArgumentException("Unknown package: " + packageName, e); } - for (UiccAccessRule rule : info.getAccessRules()) { + for (UiccAccessRule rule : info.getAllAccessRules()) { if (rule.getCarrierPrivilegeStatus(packageInfo) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return true; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 35b435d8dfb7..553bff26f78f 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -11047,6 +11047,8 @@ public class TelephonyManager { * The {@link #EXTRA_NETWORK_COUNTRY} extra indicates the country code of the current * network returned by {@link #getNetworkCountryIso()}. * + * <p>There may be a delay of several minutes before reporting that no country is detected. + * * @see #EXTRA_NETWORK_COUNTRY * @see #getNetworkCountryIso() */ diff --git a/tools/aapt2/optimize/ResourceDeduper.cpp b/tools/aapt2/optimize/ResourceDeduper.cpp index 78ebcb97b811..0278b439cfae 100644 --- a/tools/aapt2/optimize/ResourceDeduper.cpp +++ b/tools/aapt2/optimize/ResourceDeduper.cpp @@ -63,13 +63,14 @@ class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor { // Compare compatible configs for this entry and ensure the values are // equivalent. const ConfigDescription& node_configuration = node_value->config; - for (const auto& sibling : entry_->values) { - if (!sibling->value) { + for (const auto& sibling : parent->children()) { + ResourceConfigValue* sibling_value = sibling->value(); + if (!sibling_value->value) { // Sibling was already removed. continue; } - if (node_configuration.IsCompatibleWith(sibling->config) && - !node_value->value->Equals(sibling->value.get())) { + if (node_configuration.IsCompatibleWith(sibling_value->config) && + !node_value->value->Equals(sibling_value->value.get())) { // The configurations are compatible, but the value is // different, so we can't remove this value. return; diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp index 2e098aec4f8d..048e318d2802 100644 --- a/tools/aapt2/optimize/ResourceDeduper_test.cpp +++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp @@ -80,11 +80,58 @@ TEST(ResourceDeduperTest, DifferentValuesAreKept) { .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config)); EXPECT_THAT(table, HasValue("android:string/keep", land_config)); } +TEST(ResourceDeduperTest, SameValuesAreDedupedIncompatibleSiblings) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night"); + // Chosen because this configuration is not compatible with ldrtl-night. + const ConfigDescription ldrtl_notnight_config = test::ParseConfigOrDie("ldrtl-notnight"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_notnight_config, "keep2") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config))); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_notnight_config)); +} + +TEST(ResourceDeduperTest, SameValuesAreDedupedCompatibleNonSiblings) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night"); + // Chosen because this configuration is compatible with ldrtl. + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, land_config, "keep2") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config))); + EXPECT_THAT(table, HasValue("android:string/keep", land_config)); +} + TEST(ResourceDeduperTest, LocalesValuesAreKept) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const ConfigDescription default_config = {}; diff --git a/wifi/java/android/net/wifi/rtt/ResponderLocation.java b/wifi/java/android/net/wifi/rtt/ResponderLocation.java index e1d82f8d3a09..970a75d7c418 100644 --- a/wifi/java/android/net/wifi/rtt/ResponderLocation.java +++ b/wifi/java/android/net/wifi/rtt/ResponderLocation.java @@ -605,11 +605,11 @@ public final class ResponderLocation implements Parcelable { // Negative 2's complement value // Note: The Math.pow(...) method cannot return a NaN value because the bitFieldSize // for Lat or Lng is limited to exactly 34 bits. - angle = Math.scalb(fields[offset] - Math.pow(2, bitFieldSizes[offset]), + angle = Math.scalb((double) fields[offset] - Math.pow(2, bitFieldSizes[offset]), -LATLNG_FRACTION_BITS); } else { // Positive 2's complement value - angle = Math.scalb(fields[offset], -LATLNG_FRACTION_BITS); + angle = Math.scalb((double) fields[offset], -LATLNG_FRACTION_BITS); } if (angle > limit) { angle = limit; @@ -732,10 +732,11 @@ public final class ResponderLocation implements Parcelable { int maxBssidIndicator = (int) buffer[SUBELEMENT_BSSID_MAX_INDICATOR_INDEX] & BYTE_MASK; int bssidListLength = (buffer.length - 1) / BYTES_IN_A_BSSID; - // Check the max number of BSSIDs agrees with the list length. - if (maxBssidIndicator != bssidListLength) { - return false; - } + // The maxBSSIDIndicator is ignored. Its use is still being clarified in 802.11REVmd, + // which is not published at this time. This field will be used in a future + // release of Android after 802.11REVmd is public. Here, we interpret the following + // params as an explicit list of BSSIDs. + int bssidOffset = SUBELEMENT_BSSID_LIST_INDEX; for (int i = 0; i < bssidListLength; i++) { diff --git a/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java index 47c304097372..b02eebbe9a01 100644 --- a/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java +++ b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java @@ -37,7 +37,7 @@ import java.util.List; */ @RunWith(JUnit4.class) public class ResponderLocationTest { - private static final double LATLNG_TOLERANCE_DEGREES = 0.00001; + private static final double LATLNG_TOLERANCE_DEGREES = 0.000_000_05D; // 5E-8 = 6mm of meridian private static final double ALT_TOLERANCE_METERS = 0.01; private static final double HEIGHT_TOLERANCE_METERS = 0.01; private static final int INDEX_ELEMENT_TYPE = 2; @@ -103,7 +103,7 @@ public class ResponderLocationTest { private static final byte[] sTestBssidListSE = { (byte) 0x07, // Subelement BSSID list (byte) 13, // length dependent on number of BSSIDs in list - (byte) 0x02, // Number of BSSIDs in list + (byte) 0x00, // List is explicit; no expansion of list required (byte) 0x01, // BSSID #1 (MSB) (byte) 0x02, (byte) 0x03, @@ -266,11 +266,11 @@ public class ResponderLocationTest { assertTrue(valid); assertTrue(lciValid); assertFalse(zValid); - assertEquals(0.0009765625, responderLocation.getLatitudeUncertainty()); - assertEquals(-33.857009, responderLocation.getLatitude(), + assertEquals(0.0009765625D, responderLocation.getLatitudeUncertainty()); + assertEquals(-33.8570095D, responderLocation.getLatitude(), LATLNG_TOLERANCE_DEGREES); - assertEquals(0.0009765625, responderLocation.getLongitudeUncertainty()); - assertEquals(151.215200, responderLocation.getLongitude(), + assertEquals(0.0009765625D, responderLocation.getLongitudeUncertainty()); + assertEquals(151.2152005D, responderLocation.getLongitude(), LATLNG_TOLERANCE_DEGREES); assertEquals(1, responderLocation.getAltitudeType()); assertEquals(64.0, responderLocation.getAltitudeUncertainty()); @@ -282,11 +282,11 @@ public class ResponderLocationTest { assertEquals(1, responderLocation.getLciVersion()); // Testing Location Object - assertEquals(-33.857009, location.getLatitude(), + assertEquals(-33.8570095D, location.getLatitude(), LATLNG_TOLERANCE_DEGREES); - assertEquals(151.215200, location.getLongitude(), + assertEquals(151.2152005D, location.getLongitude(), LATLNG_TOLERANCE_DEGREES); - assertEquals((0.0009765625 + 0.0009765625) / 2, location.getAccuracy(), + assertEquals((0.0009765625D + 0.0009765625D) / 2, location.getAccuracy(), LATLNG_TOLERANCE_DEGREES); assertEquals(11.2, location.getAltitude(), ALT_TOLERANCE_METERS); assertEquals(64.0, location.getVerticalAccuracyMeters(), ALT_TOLERANCE_METERS); |