diff options
508 files changed, 17996 insertions, 2786 deletions
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 4e2b6fa56b17..d189bab85195 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -24,7 +24,6 @@ import android.app.GrantedUriPermission; import android.app.IApplicationThread; import android.app.IActivityClientController; import android.app.IActivityController; -import android.app.IAppTask; import android.app.IAssistDataReceiver; import android.app.IInstrumentationWatcher; import android.app.IProcessObserver; @@ -107,14 +106,6 @@ interface IActivityTaskManager { in ProfilerInfo profilerInfo, in Bundle options, int userId); boolean startNextMatchingActivity(in IBinder callingActivity, in Intent intent, in Bundle options); - - /** - * The DreamActivity has to be started in a special way that does not involve the PackageParser. - * The DreamActivity is a framework component inserted in the dream application process. Hence, - * it is not declared in the application's manifest and cannot be parsed. startDreamActivity - * creates the activity and starts it without reaching out to the PackageParser. - */ - boolean startDreamActivity(in Intent intent); int startActivityIntentSender(in IApplicationThread caller, in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 0e78275fa30b..8dd50f0c42e8 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -250,6 +250,16 @@ public abstract class DevicePolicyManagerInternal { public abstract ComponentName getProfileOwnerAsUser(@UserIdInt int userId); /** + * Returns the device owner component for the device, or {@code null} if there is not one. + * + * @deprecated added temporarily to support Android Role permission granting. + * Please contact Android Enterprise Device Policy team before calling this function. + */ + @Deprecated + @Nullable + public abstract ComponentName getDeviceOwnerComponent(boolean callingUserOnly); + + /** * Returns the user id of the device owner, or {@link UserHandle#USER_NULL} if there is not one. */ @UserIdInt diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 3d76b28a3ccc..d7195a76d873 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -65,7 +65,7 @@ public abstract class BroadcastReceiver { * thread of your app. * * <p>Note on threading: the state inside of this class is not itself - * thread-safe, however you can use it from any thread if you properly + * thread-safe. However, you can use it from any thread if you make * sure that you do not have races. Typically this means you will hand * the entire object to another thread, which will be solely responsible * for setting any results and finally calling {@link #finish()}. diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 47a5db8ea22a..3fdd023a6b68 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -770,6 +770,11 @@ interface IPackageManager { void setSplashScreenTheme(String packageName, String themeName, int userId); + int getUserMinAspectRatio(String packageName, int userId); + + @EnforcePermission("INSTALL_PACKAGES") + void setUserMinAspectRatio(String packageName, int userId, int aspectRatio); + List<String> getMimeGroup(String packageName, String group); boolean isAutoRevokeWhitelisted(String packageName); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 105b38a40825..4b883cd4c17b 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -3045,10 +3045,6 @@ public class PackageInstaller { * The update ownership enforcement can only be enabled on initial installation. Set * this to {@code true} on package update is a no-op. * - * Apps may opt themselves out of update ownership by setting the - * <a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#allowupdateownership">android:alllowUpdateOwnership</a> - * attribute in their manifest to <code>false</code>. - * * Note: To enable the update ownership enforcement, the installer must have the * {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP} * permission. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8fafb18393ef..66aadac6295d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2342,6 +2342,64 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130; + /** + * App minimum aspect ratio set by the user which will override app-defined aspect ratio. + * + * @hide + */ + @IntDef(prefix = { "USER_MIN_ASPECT_RATIO_" }, value = { + USER_MIN_ASPECT_RATIO_UNSET, + USER_MIN_ASPECT_RATIO_SPLIT_SCREEN, + USER_MIN_ASPECT_RATIO_DISPLAY_SIZE, + USER_MIN_ASPECT_RATIO_4_3, + USER_MIN_ASPECT_RATIO_16_9, + USER_MIN_ASPECT_RATIO_3_2, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UserMinAspectRatio {} + + /** + * No aspect ratio override has been set by user. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_UNSET = 0; + + /** + * Aspect ratio override code: user forces app to split screen aspect ratio. This is adjusted to + * half of the screen without the split screen divider. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_SPLIT_SCREEN = 1; + + /** + * Aspect ratio override code: user forces app to the aspect ratio of the device display size. + * This will be the portrait aspect ratio of the device if the app is portrait or the landscape + * aspect ratio of the device if the app is landscape. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_DISPLAY_SIZE = 2; + + /** + * Aspect ratio override code: user forces app to 4:3 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_4_3 = 3; + + /** + * Aspect ratio override code: user forces app to 16:9 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_16_9 = 4; + + /** + * Aspect ratio override code: user forces app to 3:2 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_3_2 = 5; + /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java index a7cd168690b4..690dfcf9619b 100644 --- a/core/java/android/content/res/ThemedResourceCache.java +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -137,8 +137,10 @@ abstract class ThemedResourceCache<T> { */ @UnsupportedAppUsage public void onConfigurationChange(@Config int configChanges) { - prune(configChanges); - mGeneration++; + synchronized (this) { + pruneLocked(configChanges); + mGeneration++; + } } /** @@ -214,22 +216,20 @@ abstract class ThemedResourceCache<T> { * simply prune missing weak references * @return {@code true} if the cache is completely empty after pruning */ - private boolean prune(@Config int configChanges) { - synchronized (this) { - if (mThemedEntries != null) { - for (int i = mThemedEntries.size() - 1; i >= 0; i--) { - if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { - mThemedEntries.removeAt(i); - } + private boolean pruneLocked(@Config int configChanges) { + if (mThemedEntries != null) { + for (int i = mThemedEntries.size() - 1; i >= 0; i--) { + if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { + mThemedEntries.removeAt(i); } } + } - pruneEntriesLocked(mNullThemedEntries, configChanges); - pruneEntriesLocked(mUnthemedEntries, configChanges); + pruneEntriesLocked(mNullThemedEntries, configChanges); + pruneEntriesLocked(mUnthemedEntries, configChanges); - return mThemedEntries == null && mNullThemedEntries == null - && mUnthemedEntries == null; - } + return mThemedEntries == null && mNullThemedEntries == null + && mUnthemedEntries == null; } private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries, diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 76efce56dcf0..022f3c4c3a20 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1762,6 +1762,24 @@ public final class DisplayManager { * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7 */ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; + + /** + * Key for new power controller feature flag. If enabled new DisplayPowerController will + * be used. + * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} + * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace. + * @hide + */ + String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller"; + + /** + * Key for normal brightness mode controller feature flag. + * It enables NormalBrightnessModeController. + * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} + * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace. + * @hide + */ + String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller"; } /** diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index e6bdfe1b95c4..7664bada2c28 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -112,9 +112,17 @@ public class GraphicsEnvironment { private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0; // Values for ANGLE_GL_DRIVER_SELECTION_VALUES - private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default"; - private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle"; - private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; + private enum AngleDriverChoice { + DEFAULT("default"), + ANGLE("angle"), + NATIVE("native"); + + public final String choice; + + AngleDriverChoice(String choice) { + this.choice = choice; + } + } private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; @@ -195,15 +203,16 @@ public class GraphicsEnvironment { } /** - * Query to determine if ANGLE should be used + * Query to determine the ANGLE driver choice. */ - private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) { + private AngleDriverChoice queryAngleChoice(Context context, Bundle coreSettings, + String packageName) { if (TextUtils.isEmpty(packageName)) { Log.v(TAG, "No package name specified; use the system driver"); - return false; + return AngleDriverChoice.DEFAULT; } - return shouldUseAngleInternal(context, coreSettings, packageName); + return queryAngleChoiceInternal(context, coreSettings, packageName); } private int getVulkanVersion(PackageManager pm) { @@ -424,10 +433,11 @@ public class GraphicsEnvironment { * forces a choice; * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; */ - private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) { + private AngleDriverChoice queryAngleChoiceInternal(Context context, Bundle bundle, + String packageName) { // Make sure we have a good package name if (TextUtils.isEmpty(packageName)) { - return false; + return AngleDriverChoice.DEFAULT; } // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE @@ -442,7 +452,7 @@ public class GraphicsEnvironment { } if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) { Log.v(TAG, "Turn on ANGLE for all applications."); - return true; + return AngleDriverChoice.ANGLE; } // Get the per-application settings lists @@ -465,7 +475,7 @@ public class GraphicsEnvironment { + optInPackages.size() + ", " + "number of values: " + optInValues.size()); - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } // See if this application is listed in the per-application settings list @@ -473,7 +483,7 @@ public class GraphicsEnvironment { if (pkgIndex < 0) { Log.v(TAG, packageName + " is not listed in per-application setting"); - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } mAngleOptInIndex = pkgIndex; @@ -483,14 +493,14 @@ public class GraphicsEnvironment { Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + "set to: '" + optInValue + "'"); - if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) { - return true; - } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { - return false; + if (optInValue.equals(AngleDriverChoice.ANGLE.choice)) { + return AngleDriverChoice.ANGLE; + } else if (optInValue.equals(AngleDriverChoice.NATIVE.choice)) { + return AngleDriverChoice.NATIVE; } else { // The user either chose default or an invalid value; go with the default driver or what // the game mode indicates - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } } @@ -558,7 +568,13 @@ public class GraphicsEnvironment { private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager, String packageName) { - if (!shouldUseAngle(context, bundle, packageName)) { + final AngleDriverChoice angleDriverChoice = queryAngleChoice(context, bundle, packageName); + if (angleDriverChoice == AngleDriverChoice.DEFAULT) { + return false; + } + + if (queryAngleChoice(context, bundle, packageName) == AngleDriverChoice.NATIVE) { + nativeSetAngleInfo("", true, packageName, null); return false; } @@ -627,10 +643,10 @@ public class GraphicsEnvironment { Log.d(TAG, "ANGLE package libs: " + paths); } - // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, - // and features to use. + // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the + // application package name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo(paths, false, packageName, features); + nativeSetAngleInfo(paths, false, packageName, features); return true; } @@ -652,10 +668,10 @@ public class GraphicsEnvironment { return false; } - // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, - // and features to use. + // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with + // the application package name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo("", true, packageName, features); + nativeSetAngleInfo("system", false, packageName, features); return true; } @@ -936,8 +952,8 @@ public class GraphicsEnvironment { private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries); private static native void setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); - private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName, - String[] features); + private static native void nativeSetAngleInfo(String path, boolean useNativeDriver, + String packageName, String[] features); private static native boolean setInjectLayersPrSetDumpable(); private static native void nativeToggleAngleAsSystemDriver(boolean enabled); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d425bf8ae557..820f454abe80 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12613,6 +12613,26 @@ public final class Settings { public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; /** + * The duration in milliseconds of each action, separated by commas. Ex: + * + * "18000,18000,18000,18000,0" + * + * See com.android.internal.telephony.data.DataStallRecoveryManager for more info + * @hide + */ + public static final String DSRM_DURATION_MILLIS = "dsrm_duration_millis"; + + /** + * The list of DSRM enabled actions, separated by commas. Ex: + * + * "true,true,false,true,true" + * + * See com.android.internal.telephony.data.DataStallRecoveryManager for more info + * @hide + */ + public static final String DSRM_ENABLED_ACTIONS = "dsrm_enabled_actions"; + + /** * Whether the wifi data connection should remain active even when higher * priority networks like Ethernet are active, to keep both networks. * In the case where higher priority networks are connected, wifi will be diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 5e7f5d62e256..9b19937444bd 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -26,7 +26,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.app.Activity; -import android.app.ActivityTaskManager; import android.app.AlarmManager; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; @@ -1268,9 +1267,7 @@ public class DreamService extends Service implements Window.Callback { fetchDreamLabel(this, serviceInfo, isPreviewMode)); try { - if (!ActivityTaskManager.getService().startDreamActivity(i)) { - detach(); - } + mDreamManager.startDreamActivity(i); } catch (SecurityException e) { Log.w(mTag, "Received SecurityException trying to start DreamActivity. " diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 609425c95b0d..dd8b3deabc01 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -17,6 +17,7 @@ package android.service.dreams; import android.content.ComponentName; +import android.content.Intent; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.IBinder; @@ -45,4 +46,5 @@ interface IDreamManager { void setDreamComponentsForUser(int userId, in ComponentName[] componentNames); void setSystemDreamComponent(in ComponentName componentName); void registerDreamOverlayService(in ComponentName componentName); + void startDreamActivity(in Intent intent); } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 6371da4f3776..ab9cff078ac4 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -25,7 +25,6 @@ import android.graphics.Paint; import android.graphics.text.LineBreakConfig; import android.graphics.text.LineBreaker; import android.os.Build; -import android.os.SystemProperties; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; @@ -33,7 +32,6 @@ import android.text.style.TabStopSpan; import android.util.Log; import android.util.Pools.SynchronizedPool; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -75,13 +73,6 @@ public class StaticLayout extends Layout { * default values. */ public final static class Builder { - // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. - private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3; - - // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. - private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE = - "android.phrase.linecount.threshold"; - private Builder() {} /** @@ -440,55 +431,11 @@ public class StaticLayout extends Layout { */ @NonNull public StaticLayout build() { - reviseLineBreakConfig(); StaticLayout result = new StaticLayout(this); Builder.recycle(this); return result; } - private void reviseLineBreakConfig() { - boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking(); - int wordStyle = mLineBreakConfig.getLineBreakWordStyle(); - if (autoPhraseBreaking) { - if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) { - if (shouldEnablePhraseBreaking()) { - mLineBreakConfig = LineBreakConfig.getLineBreakConfig( - mLineBreakConfig.getLineBreakStyle(), - LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, - mLineBreakConfig.getAutoPhraseBreaking()); - } - } - } - } - - private boolean shouldEnablePhraseBreaking() { - if (TextUtils.isEmpty(mText) || mWidth <= 0) { - return false; - } - int lineLimit = SystemProperties.getInt( - PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE, - DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE); - double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart, - mEnd, mPaint, mTextDir); - int lineCount = (int) Math.ceil(desiredWidth / mWidth); - if (lineCount > 0 && lineCount <= lineLimit) { - return true; - } - return false; - } - - /** - * Get the line break word style. - * - * @return The current line break word style. - * - * @hide - */ - @VisibleForTesting - public int getLineBreakWordStyle() { - return mLineBreakConfig.getLineBreakWordStyle(); - } - private CharSequence mText; private int mStart; private int mEnd; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index ff7d8bb956ca..0071a0d00105 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -86,9 +86,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST = "settings_need_connected_ble_device_for_broadcast"; - /** @hide */ - public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping"; - /** * Enable new language and keyboard settings UI * @hide @@ -225,7 +222,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true"); - DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true"); @@ -253,7 +249,6 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME); - PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD); diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 5019b85ca503..c1eacb535d57 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -797,7 +797,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/, - mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(), + mLastInsets.isRound(), false /* alwaysConsumeSystemBars */, mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags, mWindowType, mLastWindowingMode, null /* idSideMap */); mHost.dispatchWindowInsetsAnimationProgress(insets, @@ -841,7 +841,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mLastDispatchedState; } - @VisibleForTesting public boolean onStateChanged(InsetsState state) { boolean stateChanged = false; if (!CAPTION_ON_SHELL) { diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index e10184976abe..64411866f020 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -68,10 +68,16 @@ public class InsetsSource implements Parcelable { */ public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1; + /** + * Controls whether the insets provided by this source should be forcibly consumed. + */ + public static final int FLAG_FORCE_CONSUMING = 1 << 2; + @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = "FLAG_", value = { FLAG_SUPPRESS_SCRIM, FLAG_INSETS_ROUNDED_CORNER, + FLAG_FORCE_CONSUMING, }) public @interface Flags {} @@ -328,6 +334,9 @@ public class InsetsSource implements Parcelable { if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) { joiner.add("INSETS_ROUNDED_CORNER"); } + if ((flags & FLAG_FORCE_CONSUMING) != 0) { + joiner.add("FORCE_CONSUMING"); + } return joiner.toString(); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index dceae90822b8..c13b9ab0abd1 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.InsetsStateProto.DISPLAY_CUTOUT; import static android.view.InsetsStateProto.DISPLAY_FRAME; @@ -144,12 +145,18 @@ public class InsetsState implements Parcelable { boolean[] typeVisibilityMap = new boolean[Type.SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); + @InsetsType int forceConsumingTypes = 0; @InsetsType int suppressScrimTypes = 0; for (int i = mSources.size() - 1; i >= 0; i--) { final InsetsSource source = mSources.valueAt(i); + final @InsetsType int type = source.getType(); + + if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) { + forceConsumingTypes |= type; + } if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) { - suppressScrimTypes |= source.getType(); + suppressScrimTypes |= type; } processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, @@ -157,7 +164,7 @@ public class InsetsState implements Parcelable { // IME won't be reported in max insets as the size depends on the EditorInfo of the IME // target. - if (source.getType() != WindowInsets.Type.ime()) { + if (type != WindowInsets.Type.ime()) { InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null ? ignoringVisibilityState.peekSource(source.getId()) : source; @@ -178,13 +185,13 @@ public class InsetsState implements Parcelable { if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) { compatInsetsTypes &= ~statusBars(); } - if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode) - && !alwaysConsumeSystemBars) { - compatInsetsTypes = 0; + if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) { + // Clear all types but forceConsumingTypes. + compatInsetsTypes &= forceConsumingTypes; } return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, - alwaysConsumeSystemBars, suppressScrimTypes, calculateRelativeCutout(frame), + forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame), calculateRelativeRoundedCorners(frame), calculateRelativePrivacyIndicatorBounds(frame), calculateRelativeDisplayShape(frame), @@ -290,9 +297,8 @@ public class InsetsState implements Parcelable { public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode, @SoftInputModeFlags int softInputMode, int windowFlags) { - if (clearsCompatInsets(windowType, windowFlags, windowingMode)) { - return Insets.NONE; - } + final boolean clearsCompatInsets = clearsCompatInsets( + windowType, windowFlags, windowingMode); final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST; final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING ? systemBars() | ime() @@ -303,6 +309,9 @@ public class InsetsState implements Parcelable { if ((source.getType() & visibleInsetsTypes) == 0) { continue; } + if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) { + continue; + } insets = Insets.max(source.calculateVisibleInsets(frame), insets); } return insets; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9316dbf8a458..bef28b2789b8 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3855,7 +3855,15 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_PENDING; mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> { mWmsRequestSyncGroupState = WMS_SYNC_MERGED; - reportDrawFinished(t, seqId); + // See b/286355097. If the current process is not system, then invoking finishDraw on + // any thread is fine since once it calls into system process, finishDrawing will run + // on a different thread. However, when the current process is system, the finishDraw in + // system server will be run on the current thread, which could result in a deadlock. + if (mWindowSession instanceof Binder) { + reportDrawFinished(t, seqId); + } else { + mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId)); + } }); if (DEBUG_BLAST) { Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName()); diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 4acaea849586..57a41619ff8d 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -84,13 +84,7 @@ public final class WindowInsets { @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds; @Nullable private final DisplayShape mDisplayShape; - /** - * In multi-window we force show the navigation bar. Because we don't want that the surface size - * changes in this mode, we instead have a flag whether the navigation bar size should always - * be consumed, so the app is treated like there is no virtual navigation bar at all. - */ - private final boolean mAlwaysConsumeSystemBars; - + private final @InsetsType int mForceConsumingTypes; private final @InsetsType int mSuppressScrimTypes; private final boolean mSystemWindowInsetsConsumed; private final boolean mStableInsetsConsumed; @@ -117,7 +111,7 @@ public final class WindowInsets { static { CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null), - createCompatVisibilityMap(createCompatTypeMap(null)), false, false, 0, null, + createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null, null, null, null, systemBars(), false); } @@ -137,7 +131,8 @@ public final class WindowInsets { @Nullable Insets[] typeMaxInsetsMap, boolean[] typeVisibilityMap, boolean isRound, - boolean alwaysConsumeSystemBars, @InsetsType int suppressScrimTypes, + @InsetsType int forceConsumingTypes, + @InsetsType int suppressScrimTypes, DisplayCutout displayCutout, RoundedCorners roundedCorners, PrivacyIndicatorBounds privacyIndicatorBounds, @@ -155,7 +150,7 @@ public final class WindowInsets { mTypeVisibilityMap = typeVisibilityMap; mIsRound = isRound; - mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + mForceConsumingTypes = forceConsumingTypes; mSuppressScrimTypes = suppressScrimTypes; mCompatInsetsTypes = compatInsetsTypes; mCompatIgnoreVisibility = compatIgnoreVisibility; @@ -178,7 +173,7 @@ public final class WindowInsets { this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap, src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, - src.mAlwaysConsumeSystemBars, src.mSuppressScrimTypes, + src.mForceConsumingTypes, src.mSuppressScrimTypes, displayCutoutCopyConstructorArgument(src), src.mRoundedCorners, src.mPrivacyIndicatorBounds, @@ -235,7 +230,7 @@ public final class WindowInsets { /** @hide */ @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { - this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, 0, + this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0, null, null, null, null, systemBars(), false /* compatIgnoreVisibility */); } @@ -556,7 +551,7 @@ public final class WindowInsets { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -607,7 +602,7 @@ public final class WindowInsets { public WindowInsets consumeSystemWindowInsets() { return new WindowInsets(null, null, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, // If the system window insets types contain displayCutout, we should also consume // it. (mCompatInsetsTypes & displayCutout()) != 0 @@ -895,8 +890,8 @@ public final class WindowInsets { /** * @hide */ - public boolean shouldAlwaysConsumeSystemBars() { - return mAlwaysConsumeSystemBars; + public @InsetsType int getForceConsumingTypes() { + return mForceConsumingTypes; } /** @@ -930,6 +925,8 @@ public final class WindowInsets { result.append("\n "); result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : ""); result.append("\n "); + result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes)); + result.append("\n "); result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes)); result.append("\n "); result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes)); @@ -1027,7 +1024,7 @@ public final class WindowInsets { ? null : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom), mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutoutConsumed ? null : mDisplayCutout == null @@ -1050,7 +1047,7 @@ public final class WindowInsets { WindowInsets that = (WindowInsets) o; return mIsRound == that.mIsRound - && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars + && mForceConsumingTypes == that.mForceConsumingTypes && mSuppressScrimTypes == that.mSuppressScrimTypes && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed && mStableInsetsConsumed == that.mStableInsetsConsumed @@ -1068,7 +1065,7 @@ public final class WindowInsets { public int hashCode() { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners, - mAlwaysConsumeSystemBars, mSuppressScrimTypes, mSystemWindowInsetsConsumed, + mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape); } @@ -1134,7 +1131,7 @@ public final class WindowInsets { private DisplayShape mDisplayShape = DisplayShape.NONE; private boolean mIsRound; - private boolean mAlwaysConsumeSystemBars; + private @InsetsType int mForceConsumingTypes; private @InsetsType int mSuppressScrimTypes; private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); @@ -1162,7 +1159,7 @@ public final class WindowInsets { mDisplayCutout = displayCutoutCopyConstructorArgument(insets); mRoundedCorners = insets.mRoundedCorners; mIsRound = insets.mIsRound; - mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; + mForceConsumingTypes = insets.mForceConsumingTypes; mSuppressScrimTypes = insets.mSuppressScrimTypes; mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds; mDisplayShape = insets.mDisplayShape; @@ -1433,7 +1430,15 @@ public final class WindowInsets { /** @hide */ @NonNull public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) { - mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + // TODO (b/277891341): Remove this and related usages. This has been replaced by + // #setForceConsumingTypes. + return this; + } + + /** @hide */ + @NonNull + public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) { + mForceConsumingTypes = forceConsumingTypes; return this; } @@ -1453,7 +1458,7 @@ public final class WindowInsets { public WindowInsets build() { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, mDisplayCutout, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(), false /* compatIgnoreVisibility */); } diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl index 8a30f8cebc3d..a11c6d0ce956 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl +++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl @@ -115,4 +115,13 @@ oneway interface IWindowMagnificationConnection { * @param callback the interface to be called. */ void setConnectionCallback(in IWindowMagnificationConnectionCallback callback); + + /** + * Notify System UI the magnification scale on the specified display for userId is changed. + * + * @param userId the user id. + * @param displayId the logical display id. + * @param scale magnification scale. + */ + void onUserMagnificationScaleChanged(int userId, int displayId, float scale); } diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index aa9225b79e41..e9d7b9b25d91 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -1118,7 +1118,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) { final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { - Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); + Log.w(TAG, "requestCursorUpdates on inactive InputConnection"); return false; } if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get() diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 438b9742f0af..27d8a8f23e46 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -156,7 +156,6 @@ import android.text.util.Linkify; import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.SparseIntArray; @@ -831,11 +830,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; - // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to - // one-way flag flipping. This is a tentative limitation during experiment and will not have the - // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option. - private boolean mUserSpeficiedLineBreakwordStyle = false; - // This is used to reflect the current user preference for changing font weight and making text // more bold. private int mFontWeightAdjustment; @@ -1546,9 +1540,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_lineBreakWordStyle: - if (a.hasValue(attr)) { - mUserSpeficiedLineBreakwordStyle = true; - } mLineBreakWordStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); break; @@ -4350,7 +4341,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: attributes.mHasLineBreakWordStyle = true; - mUserSpeficiedLineBreakwordStyle = true; attributes.mLineBreakWordStyle = appearance.getInt(attr, attributes.mLineBreakWordStyle); break; @@ -5086,7 +5076,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param lineBreakWordStyle The line-break word style for the text. */ public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { - mUserSpeficiedLineBreakwordStyle = true; if (mLineBreakWordStyle != lineBreakWordStyle) { mLineBreakWordStyle = lineBreakWordStyle; if (mLayout != null) { @@ -5122,12 +5111,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see PrecomputedText */ public @NonNull PrecomputedText.Params getTextMetricsParams() { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); return new PrecomputedText.Params(new TextPaint(mTextPaint), - LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, - autoPhraseBreaking), + LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); } @@ -5147,7 +5132,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); - mUserSpeficiedLineBreakwordStyle = true; if (mLayout != null) { nullLayouts(); requestLayout(); @@ -7077,13 +7061,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( @@ -10640,9 +10621,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // TODO: code duplication with makeSingleLayout() if (mHintLayout == null) { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, mHint.length(), mTextPaint, hintWidth) .setAlignment(alignment) @@ -10655,7 +10633,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -10759,9 +10737,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (result == null) { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 0, mTransformed.length(), mTextPaint, wantWidth) .setAlignment(alignment) @@ -10774,7 +10749,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -11132,9 +11107,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) @@ -11145,7 +11117,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setTextDirection(getTextDirectionHeuristic()) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); final StaticLayout layout = layoutBuilder.build(); diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java index 954f68633e57..2858f0a1a725 100644 --- a/core/java/android/window/WindowMetricsController.java +++ b/core/java/android/window/WindowMetricsController.java @@ -145,13 +145,13 @@ public final class WindowMetricsController { for (int i = 0; i < possibleDisplayInfos.size(); i++) { currentDisplayInfo = possibleDisplayInfos.get(i); - // Calculate max bounds for this rotation and state. - Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth, - currentDisplayInfo.logicalHeight); + // Calculate max bounds for natural rotation and state. + Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), + currentDisplayInfo.getNaturalHeight()); - // Calculate insets for the rotated max bounds. + // Calculate insets for the natural max bounds. final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0; - // Initialize insets based upon display rotation. Note any window-provided insets + // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets // will not be set. windowInsets = getWindowInsetsFromServerForDisplay( currentDisplayInfo.displayId, null /* token */, diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index f2ae973500af..611da3cec5c6 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -34,6 +34,7 @@ import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.IBinder; +import android.util.Log; import android.view.Display; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.WindowType; @@ -52,6 +53,8 @@ import android.view.WindowManagerImpl; @UiContext public abstract class WindowProviderService extends Service implements WindowProvider { + private static final String TAG = WindowProviderService.class.getSimpleName(); + private final Bundle mOptions; private final WindowTokenClient mWindowToken = new WindowTokenClient(); private final WindowContextController mController = new WindowContextController(mWindowToken); @@ -194,8 +197,16 @@ public abstract class WindowProviderService extends Service implements WindowPro public final Context createServiceBaseContext(ActivityThread mainThread, LoadedApk packageInfo) { final Context context = super.createServiceBaseContext(mainThread, packageInfo); - final Display display = context.getSystemService(DisplayManager.class) - .getDisplay(getInitialDisplayId()); + final DisplayManager displayManager = context.getSystemService(DisplayManager.class); + final int initialDisplayId = getInitialDisplayId(); + Display display = displayManager.getDisplay(initialDisplayId); + // Fallback to use the default display if the initial display to start WindowProviderService + // is detached. + if (display == null) { + Log.e(TAG, "Display with id " + initialDisplayId + " not found, falling back to " + + "DEFAULT_DISPLAY"); + display = displayManager.getDisplay(DEFAULT_DISPLAY); + } return context.createTokenContext(mWindowToken, display); } diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java index 95c3419180a9..2c493031ea8a 100644 --- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java +++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java @@ -17,7 +17,7 @@ package com.android.internal.accessibility.common; /** - * Collection of common constants for accessibility shortcut. + * Collection of common constants for accessibility magnification. */ public final class MagnificationConstants { private MagnificationConstants() {} diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 7452daa4908c..65b59790e327 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -56,6 +56,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.telecom.TelecomManager; +import android.util.Log; import android.util.Slog; import android.view.View; import android.widget.Button; @@ -124,16 +125,19 @@ public class IntentForwarderActivity extends Activity { String className = intentReceived.getComponent().getClassName(); final int targetUserId; final String userMessage; + final UserInfo managedProfile; if (className.equals(FORWARD_INTENT_TO_PARENT)) { userMessage = getForwardToPersonalMessage(); targetUserId = getProfileParent(); + managedProfile = null; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) .setSubtype(MetricsEvent.PARENT_PROFILE)); } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { userMessage = getForwardToWorkMessage(); - targetUserId = getManagedProfile(); + managedProfile = getManagedProfile(); + targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) @@ -142,6 +146,7 @@ public class IntentForwarderActivity extends Activity { Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); userMessage = null; targetUserId = UserHandle.USER_NULL; + managedProfile = null; } if (targetUserId == UserHandle.USER_NULL) { // This covers the case where there is no parent / managed profile. @@ -185,27 +190,49 @@ public class IntentForwarderActivity extends Activity { finish(); // When switching to the work profile, ask the user for consent before launching } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { - maybeShowUserConsentMiniResolver(result, newIntent, targetUserId); + maybeShowUserConsentMiniResolver(result, newIntent, managedProfile); } }, getApplicationContext().getMainExecutor()); } private void maybeShowUserConsentMiniResolver( - ResolveInfo target, Intent launchIntent, int targetUserId) { + ResolveInfo target, Intent launchIntent, UserInfo managedProfile) { if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { finish(); return; } - if (launchIntent.getBooleanExtra(EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) - && getCallingPackage() != null - && PERMISSION_GRANTED == getPackageManager().checkPermission( - INTERACT_ACROSS_USERS, getCallingPackage())) { + int targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; + String callingPackage = getCallingPackage(); + boolean privilegedCallerAskedToSkipUserConsent = + launchIntent.getBooleanExtra( + EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) + && callingPackage != null + && PERMISSION_GRANTED == getPackageManager().checkPermission( + INTERACT_ACROSS_USERS, callingPackage); + + DevicePolicyManager devicePolicyManager = + getSystemService(DevicePolicyManager.class); + ComponentName profileOwnerName = devicePolicyManager.getProfileOwnerAsUser(targetUserId); + boolean intentToLaunchProfileOwner = profileOwnerName != null + && profileOwnerName.getPackageName().equals(target.getComponentInfo().packageName); + + if (privilegedCallerAskedToSkipUserConsent || intentToLaunchProfileOwner) { + Log.i("IntentForwarderActivity", String.format( + "Skipping user consent for redirection into the managed profile for intent [%s]" + + ", privilegedCallerAskedToSkipUserConsent=[%s]" + + ", intentToLaunchProfileOwner=[%s]", + launchIntent, privilegedCallerAskedToSkipUserConsent, + intentToLaunchProfileOwner)); startActivityAsCaller(launchIntent, targetUserId); finish(); return; } + Log.i("IntentForwarderActivity", String.format( + "Showing user consent for redirection into the managed profile for intent [%s] and " + + " calling package [%s]", + launchIntent, callingPackage)); int layoutId = R.layout.miniresolver; setContentView(layoutId); @@ -245,8 +272,7 @@ public class IntentForwarderActivity extends Activity { View telephonyInfo = findViewById(R.id.miniresolver_info_section); - DevicePolicyManager devicePolicyManager = - getSystemService(DevicePolicyManager.class); + // Additional information section is work telephony specific. Therefore, it is only shown // for telephony related intents, when all sim subscriptions are in the work profile. if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent)) @@ -507,20 +533,18 @@ public class IntentForwarderActivity extends Activity { } /** - * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is - * no managed profile. + * Returns the managed profile for this device or null if there is no managed profile. * - * TODO: Remove the assumption that there is only one managed profile - * on the device. + * TODO: Remove the assumption that there is only one managed profile on the device. */ - private int getManagedProfile() { + @Nullable private UserInfo getManagedProfile() { List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); for (UserInfo userInfo : relatedUsers) { - if (userInfo.isManagedProfile()) return userInfo.id; + if (userInfo.isManagedProfile()) return userInfo; } Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE + " has been called, but there is no managed profile"); - return UserHandle.USER_NULL; + return null; } /** diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index af1fdd79169a..bb868018bc95 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -232,7 +232,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private boolean mLastHasRightStableInset = false; private boolean mLastHasLeftStableInset = false; private int mLastWindowFlags = 0; - private boolean mLastShouldAlwaysConsumeSystemBars = false; + private @InsetsType int mLastForceConsumingTypes = 0; private @InsetsType int mLastSuppressScrimTypes = 0; private int mRootScrollY = 0; @@ -1111,19 +1111,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : controller.getSystemBarsAppearance(); if (insets != null) { - mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars(); + mLastForceConsumingTypes = insets.getForceConsumingTypes(); - final boolean clearsCompatInsets = - clearsCompatInsets(attrs.type, attrs.flags, - getResources().getConfiguration().windowConfiguration - .getWindowingMode()) - && !mLastShouldAlwaysConsumeSystemBars; + @InsetsType int compatInsetsTypes = + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout(); + if (clearsCompatInsets(attrs.type, attrs.flags, + getResources().getConfiguration().windowConfiguration.getWindowingMode())) { + compatInsetsTypes &= mLastForceConsumingTypes; + } final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( WindowInsets.Type.systemBars()); - final Insets systemInsets = clearsCompatInsets + final Insets systemInsets = compatInsetsTypes == 0 ? Insets.NONE - : Insets.min(insets.getInsets(WindowInsets.Type.systemBars() - | WindowInsets.Type.displayCutout()), stableBarInsets); + : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets); mLastTopInset = systemInsets.top; mLastBottomInset = systemInsets.bottom; mLastRightInset = systemInsets.right; @@ -1208,7 +1208,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && decorFitsSystemWindows && !hideNavigation) - || (mLastShouldAlwaysConsumeSystemBars && hideNavigation); + || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0 + && hideNavigation); boolean consumingNavBar = ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 @@ -1224,13 +1225,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0 || (attrs.flags & FLAG_FULLSCREEN) != 0 || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0; - boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 - && decorFitsSystemWindows - && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 - && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 - && mForceWindowDrawsBarBackgrounds - && mLastTopInset != 0 - || (mLastShouldAlwaysConsumeSystemBars && fullscreen); + boolean consumingStatusBar = + ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 + && decorFitsSystemWindows + && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 + && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 + && mForceWindowDrawsBarBackgrounds + && mLastTopInset != 0) + || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0 + && fullscreen); int consumedTop = consumingStatusBar ? mLastTopInset : 0; int consumedRight = consumingNavBar ? mLastRightInset : 0; @@ -1434,9 +1437,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private void updateColorViewInt(final ColorViewState state, int color, int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate, boolean force, @InsetsType int requestedVisibleTypes) { + final @InsetsType int type = state.attributes.insetsType; state.present = state.attributes.isPresent( - (requestedVisibleTypes & state.attributes.insetsType) != 0 - || mLastShouldAlwaysConsumeSystemBars, + (requestedVisibleTypes & type) != 0 || (mLastForceConsumingTypes & type) != 0, mWindow.getAttributes().flags, force); boolean show = state.attributes.isVisible(state.present, color, mWindow.getAttributes().flags, force); diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index afc3cbd15f88..8fc30d1c248d 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -50,7 +50,7 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, appPackageNameChars.c_str(), vulkanVersion); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle, +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver, jstring packageName, jobjectArray featuresObj) { ScopedUtfChars pathChars(env, path); ScopedUtfChars packageNameChars(env, packageName); @@ -73,7 +73,7 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useS } } - android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle, + android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver, packageNameChars.c_str(), features); } @@ -118,7 +118,7 @@ const JNINativeMethod g_methods[] = { reinterpret_cast<void*>(setGpuStats_native)}, {"setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, - {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", + {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", reinterpret_cast<void*>(setAngleInfo_native)}, {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native)}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2f9f6ae3f3c4..a01c7b6355c1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6094,7 +6094,7 @@ <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register for callbacks when apps reach a certain usage time limit, etc. --> <permission android:name="android.permission.OBSERVE_APP_USAGE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app. <p>Not for use by third-party applications. --> @@ -6724,7 +6724,7 @@ it will be ignored. @hide --> <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows entering or exiting car mode using a specified priority. This permission is required to use UiModeManager while specifying a priority for the calling diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java index 925da4968517..0ebf03fab966 100644 --- a/core/tests/coretests/src/android/text/StaticLayoutTest.java +++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; -import android.graphics.text.LineBreakConfig; import android.os.LocaleList; import android.platform.test.annotations.Presubmit; import android.text.Layout.Alignment; @@ -926,24 +925,4 @@ public class StaticLayoutTest { assertEquals(0, layout.getHeight(true)); assertEquals(2, layout.getLineCount()); } - - @Test - public void testBuilder_autoPhraseBreaking() { - { - // setAutoPhraseBreaking true - LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder() - .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE) - .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) - .setAutoPhraseBreaking(true) - .build(); - final String text = "これが正解。"; - // Obtain. - StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, - text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); - builder.setLineBreakConfig(lineBreakConfig); - builder.build(); - assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, - builder.getLineBreakWordStyle()); - } - } } diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index b4ba23c92a22..69abf5f3204f 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -40,14 +40,14 @@ public class WindowInsetsTest { @Test public void systemWindowInsets_afterConsuming_isConsumed() { assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null, - null, false, false, 0, null, null, null, null, + null, false, 0, 0, null, null, null, null, WindowInsets.Type.systemBars(), false) .consumeSystemWindowInsets().isConsumed()); } @Test public void multiNullConstructor_isConsumed() { - assertTrue(new WindowInsets(null, null, null, false, false, 0, null, null, null, null, + assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null, WindowInsets.Type.systemBars(), false).isConsumed()); } @@ -63,7 +63,7 @@ public class WindowInsetsTest { boolean[] visible = new boolean[SIZE]; WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0)); WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0)); - WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, + WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0, 0, null, null, null, DisplayShape.NONE, systemBars(), true /* compatIgnoreVisibility */); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java index a1a4265cd0a5..84dd2740e8b7 100644 --- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -169,7 +169,7 @@ public class ActionBarOverlayLayoutTest { private WindowInsets insetsWith(Insets content, DisplayCutout cutout) { return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null, - false, false, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false); + false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false); } private ViewGroup createViewGroupWithId(int id) { diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index d0327159b04d..0c493f5a1fdc 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -94,11 +94,6 @@ public final class LineBreakConfig { private @LineBreakWordStyle int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; - // Whether or not enabling phrase breaking automatically. - // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after - // the experiment. - private boolean mAutoPhraseBreaking = false; - /** * Builder constructor. */ @@ -128,22 +123,12 @@ public final class LineBreakConfig { } /** - * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}. - * - * @hide - */ - public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) { - mAutoPhraseBreaking = autoPhraseBreaking; - return this; - } - - /** * Builds a {@link LineBreakConfig} instance. * * @return The {@code LineBreakConfig} instance. */ public @NonNull LineBreakConfig build() { - return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking); + return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle); } } @@ -164,23 +149,6 @@ public final class LineBreakConfig { .build(); } - /** - * Create the LineBreakConfig instance. - * - * @param lineBreakStyle the line break style for text wrapping. - * @param lineBreakWordStyle the line break word style for text wrapping. - * @return the {@link LineBreakConfig} instance. * - * @hide - */ - public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { - LineBreakConfig.Builder builder = new LineBreakConfig.Builder(); - return builder.setLineBreakStyle(lineBreakStyle) - .setLineBreakWordStyle(lineBreakWordStyle) - .setAutoPhraseBreaking(autoPhraseBreaking) - .build(); - } - /** @hide */ public static final LineBreakConfig NONE = new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE) @@ -188,7 +156,6 @@ public final class LineBreakConfig { private final @LineBreakStyle int mLineBreakStyle; private final @LineBreakWordStyle int mLineBreakWordStyle; - private final boolean mAutoPhraseBreaking; /** * Constructor with line-break parameters. @@ -197,10 +164,9 @@ public final class LineBreakConfig { * {@code LineBreakConfig} instance. */ private LineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { + @LineBreakWordStyle int lineBreakWordStyle) { mLineBreakStyle = lineBreakStyle; mLineBreakWordStyle = lineBreakWordStyle; - mAutoPhraseBreaking = autoPhraseBreaking; } /** @@ -221,17 +187,6 @@ public final class LineBreakConfig { return mLineBreakWordStyle; } - /** - * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. - * - * @return The result that records whether or not the automation of - * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. - * @hide - */ - public boolean getAutoPhraseBreaking() { - return mAutoPhraseBreaking; - } - @Override public boolean equals(Object o) { if (o == null) return false; @@ -239,8 +194,7 @@ public final class LineBreakConfig { if (!(o instanceof LineBreakConfig)) return false; LineBreakConfig that = (LineBreakConfig) o; return (mLineBreakStyle == that.mLineBreakStyle) - && (mLineBreakWordStyle == that.mLineBreakWordStyle) - && (mAutoPhraseBreaking == that.mAutoPhraseBreaking); + && (mLineBreakWordStyle == that.mLineBreakWordStyle); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 262d487b1d1b..2dbc4445d606 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -434,7 +434,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive", from); mInteractive = interactive; - if (!mInteractive && mMoving) { + if (!mInteractive && hideHandle && mMoving) { final int position = mSplitLayout.getDividePosition(); mSplitLayout.flingDividePosition( mLastDraggingPosition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index a9ccdf6a156f..2b1037711249 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -323,7 +323,11 @@ public class SplitDecorManager extends WindowlessWindowManager { } } if (mShown) { - fadeOutDecor(()-> animFinishedCallback.accept(true)); + fadeOutDecor(()-> { + if (mRunningAnimationCount == 0 && animFinishedCallback != null) { + animFinishedCallback.accept(true); + } + }); } else { // Decor surface is hidden so release it directly. releaseDecor(t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index f70d3aec9ec8..e8fa638bea31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -593,9 +593,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange void flingDividePosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback) { if (from == to) { - // No animation run, still callback to stop resizing. - mSplitLayoutHandler.onLayoutSizeChanged(this); - if (flingFinishedCallback != null) { flingFinishedCallback.run(); } @@ -773,15 +770,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) { boolean boundsChanged = false; if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) { - wct.setBounds(task1.token, mBounds1); - wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1)); + setTaskBounds(wct, task1, mBounds1); mWinBounds1.set(mBounds1); mWinToken1 = task1.token; boundsChanged = true; } if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) { - wct.setBounds(task2.token, mBounds2); - wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2)); + setTaskBounds(wct, task2, mBounds2); mWinBounds2.set(mBounds2); mWinToken2 = task2.token; boundsChanged = true; @@ -789,6 +784,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return boundsChanged; } + /** Set bounds to the {@link WindowContainerTransaction} for single task. */ + public void setTaskBounds(WindowContainerTransaction wct, + ActivityManager.RunningTaskInfo task, Rect bounds) { + wct.setBounds(task.token, bounds); + wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds)); + } + private int getSmallestWidthDp(Rect bounds) { mTempRect.set(bounds); mTempRect.inset(getDisplayStableInsets(mContext)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java index 0289da916937..d7ea1c0c620d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -89,4 +89,9 @@ public class SplitScreenUtils { int userId1, int userId2) { return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2); } + + /** Generates a common log message for split screen failures */ + public static String splitFailureMessage(String caller, String reason) { + return "(" + caller + ") Splitscreen aborted: " + reason; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 4fda4b7896c2..40ea2764d295 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -286,7 +286,7 @@ class DesktopTasksController( visualIndicator?.releaseVisualIndicator(t) visualIndicator = null addMoveToFullscreenChanges(callbackWCT, task) - shellTaskOrganizer.applyTransaction(callbackWCT) + transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */) } } else { addMoveToFullscreenChanges(wct, task) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index d6451d75a012..73eb62ae47e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -339,19 +339,36 @@ public class PipTransition extends PipTransitionController { } // This means an expand happened before enter-pip finished and we are now "merging" a // no-op transition that happens to match our exit-pip. + // Or that the keyguard is up and preventing the transition from applying, in which case we + // want to manually reset pip. (b/283783868) boolean cancelled = false; if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); cancelled = true; } + // Unset exitTransition AFTER cancel so that finishResize knows we are merging. mExitTransition = null; - if (!cancelled || aborted) return; + if (!cancelled) return; final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (taskInfo != null) { - startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), - mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), - new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + if (aborted) { + // keyguard case - the transition got aborted, so we want to reset state and + // windowing mode before reapplying the resize transaction + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + mPipOrganizer.onExitPipFinished(taskInfo); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); + wct.setBounds(taskInfo.token, null); + mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false); + } else { + // merge case + startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), + mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), + new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + } } mExitDestinationBounds.setEmpty(); mCurrentPipTaskToken = null; @@ -840,7 +857,7 @@ public class PipTransition extends PipTransitionController { } final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); - final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); + final Rect currentBounds = pipChange.getStartAbsBounds(); int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds, destinationBounds); @@ -1059,7 +1076,7 @@ public class PipTransition extends PipTransitionController { // When the PIP window is visible and being a part of the transition, such as display // rotation, we need to update its bounds and rounded corner. final SurfaceControl leash = pipChange.getLeash(); - final Rect destBounds = mPipBoundsState.getBounds(); + final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds(); final boolean isInPip = mPipTransitionState.isInPip(); mSurfaceTransactionHelper .crop(startTransaction, leash, destBounds) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index e294229038f2..3669bceebc54 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -31,6 +31,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage; +import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -50,6 +51,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.Log; import android.util.Slog; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; @@ -551,6 +553,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startShortcut", + "app package " + packageName + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); return; @@ -580,6 +584,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -612,6 +618,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startShortcutAndTask", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -647,6 +655,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -677,6 +687,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntentAndTask", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -705,6 +717,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, pendingIntent2 = null; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -734,6 +748,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, pendingIntent2 = null; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntents", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); } @@ -780,6 +796,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); + Log.w(TAG, splitFailureMessage("startIntent", + "app package " + packageName1 + " does not support multi-instance")); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT).show(); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index acc1c5eb74b6..6961dabbb4b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -28,6 +28,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; @@ -41,6 +42,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; +import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -473,6 +475,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isEnteringSplit && mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen( null /* childrenToTop */, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("startShortcut", + "side stage was not populated")); mSplitUnsupportedToast.show(); } @@ -560,6 +564,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isEnteringSplit && mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen( null /* childrenToTop */, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("startIntentLegacy", + "side stage was not populated")); mSplitUnsupportedToast.show(); } @@ -1090,6 +1096,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled", + "main or side stage was not populated.")); mSplitUnsupportedToast.show(); } else { mSyncQueue.queue(evictWct); @@ -1109,6 +1117,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished", + "main or side stage was not populated")); mSplitUnsupportedToast.show(); return; } @@ -1293,20 +1303,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible; final boolean oneStageVisible = mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible; - if (oneStageVisible) { + if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) { // Dismiss split because there's show-when-locked activity showing on top of keyguard. // Also make sure the task contains show-when-locked activity remains on top after split // dismissed. - if (!ENABLE_SHELL_TRANSITIONS) { - final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; - exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); - } else { - final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(dismissTop, wct); - mSplitTransitions.startDismissTransition(wct, this, dismissTop, - EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); - } + final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; + exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } } @@ -1561,6 +1563,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // split bounds. wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); + mSplitLayout.getInvisibleBounds(mTempRect1); + mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1); } wct.reorder(mRootTaskInfo.token, true); setRootForceTranslucent(false, wct); @@ -2376,6 +2380,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // so appends operations to exit split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); } + } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null + && isSplitScreenVisible()) { + // Split include show when lock activity case, check the top activity under which + // stage and move it to the top. + int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity) + ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + prepareExitSplitScreen(top, out); + mSplitTransitions.setDismissTransition(transition, top, + EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } // When split in the background, it should be only opening/dismissing transition and @@ -2704,12 +2717,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } else { if (mainChild == null || sideChild == null) { - Log.w(TAG, "Launched 2 tasks in split, but didn't receive" - + " 2 tasks in transition. Possibly one of them failed to launch"); final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); mSplitTransitions.mPendingEnter.cancel( (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); + Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", + "launched 2 tasks in split, but didn't receive " + + "2 tasks in transition. Possibly one of them failed to launch")); mSplitUnsupportedToast.show(); return true; } @@ -2853,18 +2867,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + final ArrayMap<Integer, SurfaceControl> dismissingTasks = new ArrayMap<>(); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null) continue; + if (getStageOfTask(taskInfo) != null + || getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) { + dismissingTasks.put(taskInfo.taskId, change.getLeash()); + } + } + + if (shouldBreakPairedTaskInRecents(dismissReason)) { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again mRecentTasks.ifPresent(recentTasks -> { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null && (getStageOfTask(taskInfo) != null - || getSplitItemPosition(change.getLastParent()) - != SPLIT_POSITION_UNDEFINED)) { - recentTasks.removeSplitPair(taskInfo.taskId); - } + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + recentTasks.removeSplitPair(dismissingTasks.keyAt(i)); } }); } @@ -2882,6 +2902,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); t.setPosition(toStage == STAGE_TYPE_MAIN ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); + } else { + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + finishT.hide(dismissingTasks.valueAt(i)); + } } if (toStage == STAGE_TYPE_UNDEFINED) { @@ -2891,7 +2915,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // Hide divider and dim layer on transition finished. - setDividerVisibility(false, finishT); + setDividerVisibility(false, t); finishT.hide(mMainStage.mDimLayer); finishT.hide(mSideStage.mDimLayer); } @@ -3159,7 +3183,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onNoLongerSupportMultiWindow() { + public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) { if (mMainStage.isActive()) { final boolean isMainStage = mMainStageListener == this; if (!ENABLE_SHELL_TRANSITIONS) { @@ -3174,6 +3198,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareExitSplitScreen(stageType, wct); mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); + Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow", + "app package " + taskInfo.baseActivity.getPackageName() + + " does not support splitscreen, or is a controlled activity type")); mSplitUnsupportedToast.show(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 3ef4f024a8ea..e2c55e4c63ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -81,7 +81,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onRootTaskVanished(); - void onNoLongerSupportMultiWindow(); + void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo); } private final Context mContext; @@ -226,7 +226,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskInfo.getWindowingMode())) { // Leave split screen if the task no longer supports multi window or have // uncontrolled task. - mCallbacks.onNoLongerSupportMultiWindow(); + mCallbacks.onNoLongerSupportMultiWindow(taskInfo); return; } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index c964df1452e0..c2f15f6cba75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -16,6 +16,7 @@ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,13 @@ public class TaskSnapshotWindow { @NonNull Runnable clearWindowHandler) { final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; final int taskId = runningTaskInfo.taskId; + + // if we're in PIP we don't want to create the snapshot + if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "did not create taskSnapshot due to being in PIP"); + return null; + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index a28ce55e8c94..d9edde16a863 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -25,6 +25,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; @@ -500,6 +501,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } } + mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, finishCB); // Dispatch the rest of the transition normally. This will most-likely be taken by diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index b217bd39a446..ce8191067ae9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -145,7 +145,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mDisplay.getDisplayId(), 0 /* taskCornerRadius */, mDecorationContainerSurface, - mDragPositioningCallback); + mDragPositioningCallback, + mSurfaceControlBuilderSupplier, + mSurfaceControlTransactionSupplier); } final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index bc89385a0d13..a359395711e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -234,7 +234,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mDisplay.getDisplayId(), mRelayoutParams.mCornerRadius, mDecorationContainerSurface, - mDragPositioningCallback); + mDragPositioningCallback, + mSurfaceControlBuilderSupplier, + mSurfaceControlTransactionSupplier); } final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java index 4e98f0c3a48a..941617de3aec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java @@ -22,7 +22,9 @@ import android.annotation.IntDef; * Callback called when receiving drag-resize or drag-move related input events. */ public interface DragPositioningCallback { - @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM}) + @IntDef(flag = true, value = { + CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM + }) @interface CtrlType {} int CTRL_TYPE_UNDEFINED = 0; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index e5fc66afbdfc..336d95e7b62b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -18,8 +18,11 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; @@ -48,6 +51,8 @@ import android.view.WindowManagerGlobal; import com.android.internal.view.BaseIWindow; +import java.util.function.Supplier; + /** * An input event listener registered to InputDispatcher to receive input events on task edges and * and corners. Converts them to drag resize requests. @@ -60,6 +65,7 @@ class DragResizeInputListener implements AutoCloseable { private final Handler mHandler; private final Choreographer mChoreographer; private final InputManager mInputManager; + private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; private final int mDisplayId; private final BaseIWindow mFakeWindow; @@ -69,6 +75,10 @@ class DragResizeInputListener implements AutoCloseable { private final TaskResizeInputEventReceiver mInputEventReceiver; private final DragPositioningCallback mCallback; + private final SurfaceControl mInputSinkSurface; + private final BaseIWindow mFakeSinkWindow; + private final InputChannel mSinkInputChannel; + private int mTaskWidth; private int mTaskHeight; private int mResizeHandleThickness; @@ -90,15 +100,18 @@ class DragResizeInputListener implements AutoCloseable { int displayId, int taskCornerRadius, SurfaceControl decorationSurface, - DragPositioningCallback callback) { + DragPositioningCallback callback, + Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, + Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { mInputManager = context.getSystemService(InputManager.class); mHandler = handler; mChoreographer = choreographer; + mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mDisplayId = displayId; mTaskCornerRadius = taskCornerRadius; mDecorationSurface = decorationSurface; - // Use a fake window as the backing surface is a container layer and we don't want to create - // a buffer layer for it so we can't use ViewRootImpl. + // Use a fake window as the backing surface is a container layer, and we don't want to + // create a buffer layer for it, so we can't use ViewRootImpl. mFakeWindow = new BaseIWindow(); mFakeWindow.setSession(mWindowSession); mFocusGrantToken = new Binder(); @@ -111,7 +124,7 @@ class DragResizeInputListener implements AutoCloseable { null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, - 0 /* inputFeatures */, + INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, mFocusGrantToken, @@ -126,6 +139,35 @@ class DragResizeInputListener implements AutoCloseable { mCallback = callback; mDragDetector = new DragDetector(mInputEventReceiver); mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop()); + + mInputSinkSurface = surfaceControlBuilderSupplier.get() + .setName("TaskInputSink of " + decorationSurface) + .setContainerLayer() + .setParent(mDecorationSurface) + .build(); + mSurfaceControlTransactionSupplier.get() + .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) + .show(mInputSinkSurface) + .apply(); + mFakeSinkWindow = new BaseIWindow(); + mSinkInputChannel = new InputChannel(); + try { + mWindowSession.grantInputChannel( + mDisplayId, + mInputSinkSurface, + mFakeSinkWindow, + null /* hostInputToken */, + FLAG_NOT_FOCUSABLE, + 0 /* privateFlags */, + INPUT_FEATURE_NO_INPUT_CHANNEL, + TYPE_INPUT_CONSUMER, + null /* windowToken */, + mFocusGrantToken, + "TaskInputSink of " + decorationSurface, + mSinkInputChannel); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @@ -219,7 +261,35 @@ class DragResizeInputListener implements AutoCloseable { mDecorationSurface, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, - 0 /* inputFeatures */, + INPUT_FEATURE_SPY, + touchRegion); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + mSurfaceControlTransactionSupplier.get() + .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight) + .apply(); + // The touch region of the TaskInputSink should be the touch region of this + // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent + // input windows from handling down events, which will bring tasks in the back to front. + // + // Note not the entire touch region responds to both mouse and touchscreen events. + // Therefore, in the region that only responds to one of them, it would be a no-op to + // perform a gesture in the other type of events. We currently only have a mouse-only region + // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe + // issue. However, were there touchscreen-only a region out of the task bounds, mouse + // gestures will become no-op in that region, even though the mouse gestures may appear to + // be performed on the input window behind the resize handle. + touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE); + try { + mWindowSession.updateInputChannel( + mSinkInputChannel.getToken(), + mDisplayId, + mInputSinkSurface, + FLAG_NOT_FOCUSABLE, + 0 /* privateFlags */, + INPUT_FEATURE_NO_INPUT_CHANNEL, touchRegion); } catch (RemoteException e) { e.rethrowFromSystemServer(); @@ -248,6 +318,16 @@ class DragResizeInputListener implements AutoCloseable { } catch (RemoteException e) { e.rethrowFromSystemServer(); } + + mSinkInputChannel.dispose(); + try { + mWindowSession.remove(mFakeSinkWindow); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + mSurfaceControlTransactionSupplier.get() + .remove(mInputSinkSurface) + .apply(); } private class TaskResizeInputEventReceiver extends InputEventReceiver @@ -316,6 +396,8 @@ class DragResizeInputListener implements AutoCloseable { mShouldHandleEvents = isInResizeHandleBounds(x, y); } if (mShouldHandleEvents) { + mInputManager.pilferPointers(mInputChannel.getToken()); + mDragPointerId = e.getPointerId(0); float rawX = e.getRawX(0); float rawY = e.getRawY(0); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 917abf5524a4..e1b6db595975 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -63,8 +63,6 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mDragStartListener = dragStartListener; mTransactionSupplier = supplier; mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; - mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId()) - .getStableBounds(mStableBounds); } @Override @@ -80,6 +78,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mTaskOrganizer.applyTransaction(wct); } mRepositionTaskBounds.set(mTaskBoundsAtDragStart); + if (mStableBounds.isEmpty()) { + mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId()) + .getStableBounds(mStableBounds); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index bf3ff3fa83ef..ae3b5eb6a5b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -80,8 +80,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mTransactionSupplier = supplier; mTransitions = transitions; mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; - mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId()) - .getStableBounds(mStableBounds); } @Override @@ -100,6 +98,10 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, } mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId); mRepositionTaskBounds.set(mTaskBoundsAtDragStart); + if (mStableBounds.isEmpty()) { + mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId()) + .getStableBounds(mStableBounds); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index ddc7fef0599f..0b0d9d5086f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -64,6 +64,24 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> implements AutoCloseable { /** + * The Z-order of {@link #mCaptionContainerSurface}. + * <p> + * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by + * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input + * prior to caption view itself, treating corner inputs as resize events rather than + * repositioning. + */ + static final int CAPTION_LAYER_Z_ORDER = -1; + /** + * The Z-order of the task input sink in {@link DragPositioningCallback}. + * <p> + * This task input sink is used to prevent undesired dispatching of motion events out of task + * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle + * input events first. + */ + static final int INPUT_SINK_Z_ORDER = -2; + + /** * System-wide context. Only used to create context with overridden configurations. */ final Context mContext; @@ -238,11 +256,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); - // We use mDecorationContainerSurface to define input window for task resizing; by layering - // it in front of mCaptionContainerSurface, we can allow it to handle input prior to - // caption view itself, treating corner inputs as resize events rather than repositioning. startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) - .setLayer(mCaptionContainerSurface, -1) + .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) .show(mCaptionContainerSurface); if (ViewRootImpl.CAPTION_ON_SHELL) { diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index e382a0f0aeda..89747dfa5765 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -30,19 +30,26 @@ filegroup { filegroup { name: "WMShellFlickerTestsBubbles-src", - srcs: ["src/**/bubble/*.kt"], + srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"], } filegroup { name: "WMShellFlickerTestsPip-src", - srcs: ["src/**/pip/*.kt"], + srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"], } filegroup { name: "WMShellFlickerTestsSplitScreen-src", srcs: [ - "src/**/splitscreen/*.kt", - "src/**/splitscreen/benchmark/*.kt", + "src/com/android/wm/shell/flicker/splitscreen/*.kt", + "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt", + ], +} + +filegroup { + name: "WMShellFlickerServiceTests-src", + srcs: [ + "src/com/android/wm/shell/flicker/service/**/*.kt", ], } @@ -88,6 +95,7 @@ android_test { ":WMShellFlickerTestsBubbles-src", ":WMShellFlickerTestsPip-src", ":WMShellFlickerTestsSplitScreen-src", + ":WMShellFlickerServiceTests-src", ], } @@ -126,3 +134,15 @@ android_test { ":WMShellFlickerTestsSplitScreen-src", ], } + +android_test { + name: "WMShellFlickerServiceTests", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"], + package_name: "com.android.wm.shell.flicker.service", + instrumentation_target_package: "com.android.wm.shell.flicker.service", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerServiceTests-src", + ], +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt index f4828f14d5ed..fd56a6e49d3e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.splitscreen +package com.android.wm.shell.flicker import android.app.Instrumentation import android.graphics.Point @@ -40,8 +40,6 @@ import com.android.server.wm.flicker.helpers.NotificationAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import org.junit.Assert.assertNotNull internal object SplitScreenUtils { @@ -114,13 +112,12 @@ internal object SplitScreenUtils { } fun enterSplitViaIntent( - wmHelper: WindowManagerStateHelper, - primaryApp: StandardAppHelper, - secondaryApp: StandardAppHelper + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper ) { val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true") - primaryApp.launchViaIntent(wmHelper, null, null, - stringExtras) + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt new file mode 100644 index 000000000000..d3f3c5b7c672 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.appcompat + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.Rect +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching to letterboxed app from launcher + * + * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest` + * + * Actions: + * ``` + * Launch a letterboxed app + * Navigate home to show launcher + * Swipe right from the bottom of the screen to quick switch back to the app + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : + BaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + + letterboxApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withHomeActivityVisible() + .withWindowSurfaceDisappeared(letterboxApp) + .waitForAndVerify() + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.workspace.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(letterboxApp) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + teardown { letterboxApp.exit(wmHelper) } + } + + /** + * Checks that [letterboxApp] is the top window at the end of the transition once we have fully + * quick switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithAppBeingOnTop() { + flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) } + } + + /** Checks that the transition starts with the home activity being tagged as visible. */ + @Postsubmit + @Test + fun startsWithHomeActivityFlaggedVisible() { + flicker.assertWmStart { this.isHomeActivityVisible() } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows + * filling/covering exactly display size + */ + @Postsubmit + @Test + fun startsWithLauncherWindowsCoverFullScreen() { + flicker.assertWmStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers + * filling/covering exactly the display size. + */ + @Postsubmit + @Test + fun startsWithLauncherLayersCoverFullScreen() { + flicker.assertLayersStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top + * window. + */ + @Postsubmit + @Test + fun startsWithLauncherBeingOnTop() { + flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) } + } + + /** + * Checks that the transition ends with the home activity being flagged as not visible. By this + * point we should have quick switched away from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithHomeActivityFlaggedInvisible() { + flicker.assertWmEnd { this.isHomeActivityInvisible() } + } + + /** + * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appWindowBecomesAndStaysVisible() { + flicker.assertWm { + this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp) + } + } + + /** + * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appLayerBecomesAndStaysVisible() { + flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherWindowBecomesAndStaysInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherLayerBecomesAndStaysInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isInvisible(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app + * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows + * are at least partially visible. + */ + @Postsubmit + @Test + fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer + * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at + * least partially visible. + */ + @Postsubmit + @Test + fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the + * [letterboxApp] layer is visible at the end of the transition once we have fully quick + * switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + .isVisible(ComponentNameMatcher.LETTERBOX) + } + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt index 36bbafb4c05e..8bd44c3a7fd0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt @@ -16,12 +16,9 @@ package com.android.wm.shell.flicker.pip -import android.app.Instrumentation -import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation -import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest @@ -29,13 +26,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.UiObject2 -import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -73,8 +66,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Second app used to enter split screen mode */ - protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation) - fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper = + private val secondAppForSplitScreen = SimpleAppHelper( instrumentation, ActivityOptions.SplitScreen.Primary.LABEL, @@ -88,14 +80,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : secondAppForSplitScreen.launchViaIntent(wmHelper) pipApp.launchViaIntent(wmHelper) tapl.goHome() - enterSplitScreen() - // wait until split screen is established - wmHelper - .StateSyncBuilder() - .withWindowSurfaceAppeared(pipApp) - .withWindowSurfaceAppeared(secondAppForSplitScreen) - .withSplitDividerVisible() - .waitForAndVerify() + SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen) pipApp.enableAutoEnterForPipActivity() } teardown { @@ -107,46 +92,6 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : transitions { tapl.goHome() } } - // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils - private val TIMEOUT_MS = 3_000L - private val overviewSnapshotSelector: BySelector - get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot") - private fun enterSplitScreen() { - // Note: The initial split position in landscape is different between tablet and phone. - // In landscape, tablet will let the first app split to right side, and phone will - // split to left side. - if (tapl.isTablet) { - // TAPL's currentTask on tablet is sometimes not what we expected if the overview - // contains more than 3 task views. We need to use uiautomator directly to find the - // second task to split. - tapl.workspace.switchToOverview().overviewActions.clickSplit() - val snapshots = - tapl.device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) - if (snapshots == null || snapshots.size < 1) { - error("Fail to find a overview snapshot to split.") - } - - // Find the second task in the upper right corner in split select mode by sorting - // 'left' in descending order and 'top' in ascending order. - snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> - t2.getVisibleBounds().left - t1.getVisibleBounds().left - } - snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> - t1.getVisibleBounds().top - t2.getVisibleBounds().top - } - snapshots[0].click() - } else { - tapl.workspace - .switchToOverview() - .currentTask - .tapMenu() - .tapSplitMenuItem() - .currentTask - .open() - } - SystemClock.sleep(TIMEOUT_MS) - } - @Presubmit @Test override fun pipOverlayLayerAppearThenDisappear() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt new file mode 100644 index 000000000000..e640dc404623 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/SplitScreenUtils.kt @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen + +import android.app.Instrumentation +import android.graphics.Point +import android.os.SystemClock +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.IComponentMatcher +import android.tools.common.traces.component.IComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import org.junit.Assert.assertNotNull + +object SplitScreenUtils { + private const val TIMEOUT_MS = 3_000L + private const val DRAG_DURATION_MS = 1_000L + private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" + private const val DIVIDER_BAR = "docked_divider_handle" + private const val OVERVIEW_SNAPSHOT = "snapshot" + private const val GESTURE_STEP_MS = 16L + private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L + private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + + private val notificationScrollerSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) + private val notificationContentSelector: BySelector + get() = By.text("Flicker Test Notification") + private val dividerBarSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) + private val overviewSnapshotSelector: BySelector + get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT) + + fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) + + fun getSecondary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Secondary.LABEL, + ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent() + ) + + fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper = + NonResizeableAppHelper(instrumentation) + + fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper = + NotificationAppHelper(instrumentation) + + fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation) + + fun waitForSplitComplete( + wmHelper: WindowManagerStateHelper, + primaryApp: IComponentMatcher, + secondaryApp: IComponentMatcher, + ) { + wmHelper + .StateSyncBuilder() + .withWindowSurfaceAppeared(primaryApp) + .withWindowSurfaceAppeared(secondaryApp) + .withSplitDividerVisible() + .waitForAndVerify() + } + + fun enterSplit( + wmHelper: WindowManagerStateHelper, + tapl: LauncherInstrumentation, + device: UiDevice, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + splitFromOverview(tapl, device) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) { + // Note: The initial split position in landscape is different between tablet and phone. + // In landscape, tablet will let the first app split to right side, and phone will + // split to left side. + if (tapl.isTablet) { + // TAPL's currentTask on tablet is sometimes not what we expected if the overview + // contains more than 3 task views. We need to use uiautomator directly to find the + // second task to split. + tapl.workspace.switchToOverview().overviewActions.clickSplit() + val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) + if (snapshots == null || snapshots.size < 1) { + error("Fail to find a overview snapshot to split.") + } + + // Find the second task in the upper right corner in split select mode by sorting + // 'left' in descending order and 'top' in ascending order. + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t2.getVisibleBounds().left - t1.getVisibleBounds().left + } + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t1.getVisibleBounds().top - t2.getVisibleBounds().top + } + snapshots[0].click() + } else { + tapl.workspace + .switchToOverview() + .currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() + } + SystemClock.sleep(TIMEOUT_MS) + } + + fun enterSplitViaIntent( + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + val stringExtras = + mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true") + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun dragFromNotificationToSplit( + instrumentation: Instrumentation, + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + + // Pull down the notifications + device.swipe( + displayBounds.centerX(), + 5, + displayBounds.centerX(), + displayBounds.bottom, + 50 /* steps */ + ) + SystemClock.sleep(TIMEOUT_MS) + + // Find the target notification + val notificationScroller = + device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS) + ?: error("Unable to find view $notificationScrollerSelector") + var notificationContent = notificationScroller.findObject(notificationContentSelector) + + while (notificationContent == null) { + device.swipe( + displayBounds.centerX(), + displayBounds.centerY(), + displayBounds.centerX(), + displayBounds.centerY() - 150, + 20 /* steps */ + ) + notificationContent = notificationScroller.findObject(notificationContentSelector) + } + + // Drag to split + val dragStart = notificationContent.visibleCenter + val dragMiddle = Point(dragStart.x + 50, dragStart.y) + val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val downTime = SystemClock.uptimeMillis() + + touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart) + // It needs a horizontal movement to trigger the drag + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragStart, + dragMiddle + ) + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragMiddle, + dragEnd + ) + // Wait for a while to start splitting + SystemClock.sleep(TIMEOUT_MS) + touch( + instrumentation, + MotionEvent.ACTION_UP, + downTime, + SystemClock.uptimeMillis(), + GESTURE_STEP_MS, + dragEnd + ) + SystemClock.sleep(TIMEOUT_MS) + } + + fun touch( + instrumentation: Instrumentation, + action: Int, + downTime: Long, + eventTime: Long, + duration: Long, + point: Point + ) { + val motionEvent = + MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionEvent, true) + motionEvent.recycle() + SystemClock.sleep(duration) + } + + fun touchMove( + instrumentation: Instrumentation, + downTime: Long, + eventTime: Long, + duration: Long, + from: Point, + to: Point + ) { + val steps: Long = duration / GESTURE_STEP_MS + var currentTime = eventTime + var currentX = from.x.toFloat() + var currentY = from.y.toFloat() + val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() + val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() + + for (i in 1..steps) { + val motionMove = + MotionEvent.obtain( + downTime, + currentTime, + MotionEvent.ACTION_MOVE, + currentX, + currentY, + 0 + ) + motionMove.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionMove, true) + motionMove.recycle() + + currentTime += GESTURE_STEP_MS + if (i == steps - 1) { + currentX = to.x.toFloat() + currentY = to.y.toFloat() + } else { + currentX += stepX + currentY += stepY + } + SystemClock.sleep(GESTURE_STEP_MS) + } + } + + fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) { + tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) + val allApps = tapl.workspace.switchToAllApps() + allApps.freeze() + try { + allApps.getAppIcon(appName).dragToHotseat(0) + } finally { + allApps.unfreeze() + } + } + + fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200) + + wmHelper + .StateSyncBuilder() + .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) + .waitForAndVerify() + } + + fun dragDividerToDismissSplit( + device: UiDevice, + wmHelper: WindowManagerStateHelper, + dragToRight: Boolean, + dragToBottom: Boolean + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag( + Point( + if (dragToRight) { + displayBounds.width * 4 / 5 + } else { + displayBounds.width * 1 / 5 + }, + if (dragToBottom) { + displayBounds.height * 4 / 5 + } else { + displayBounds.height * 1 / 5 + } + ) + ) + } + + fun doubleTapDividerToSwitch(device: UiDevice) { + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + val interval = + (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2 + dividerBar.click() + SystemClock.sleep(interval.toLong()) + dividerBar.click() + } + + fun copyContentInSplit( + instrumentation: Instrumentation, + device: UiDevice, + sourceApp: IComponentNameMatcher, + destinationApp: IComponentNameMatcher, + ) { + // Copy text from sourceApp + val textView = + device.wait( + Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the TextView", textView) + textView.click(LONG_PRESS_TIME_MS) + + val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) + assertNotNull("Unable to find the copy button", copyBtn) + copyBtn.click() + + // Paste text to destinationApp + val editText = + device.wait( + Until.findObject(By.res(destinationApp.packageName, "plain_text_input")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the EditText", editText) + editText.click(LONG_PRESS_TIME_MS) + + val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) + assertNotNull("Unable to find the paste button", pasteBtn) + pasteBtn.click() + + // Verify text + if (!textView.text.contentEquals(editText.text)) { + error("Fail to copy content in split") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..566adec75615 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +@RequiresDevice +class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun copyContentInSplit() = super.copyContentInSplit() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..92b62273d8cb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +@RequiresDevice +class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun copyContentInSplit() = super.copyContentInSplit() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..e6d56b5c94d3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark : + DismissSplitScreenByDivider(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..6752c58bd568 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByDividerGesturalNavPortraitBenchmark : + DismissSplitScreenByDivider(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..7c9ab9939dd0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark : + DismissSplitScreenByGoHome(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..4b795713cb23 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark : + DismissSplitScreenByGoHome(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..04950799732e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +@RequiresDevice +class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dragDividerToResize() = super.dragDividerToResize() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..71ef48bea686 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +@RequiresDevice +class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dragDividerToResize() = super.dragDividerToResize() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..c78729c6dc92 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..30bce2f657b1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..b33ea7c89158 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..07a86a57117b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..9a1d12787b9d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..266e268a3537 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..83fc30bceb7b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b2f19299c7f0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..dae92dddbfec --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark : + EnterSplitScreenFromOverview(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..732047ba38ad --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark : + EnterSplitScreenFromOverview(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..1de7efd7970a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +@RequiresDevice +class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..1a046aa5b09e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +@RequiresDevice +class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..6e88f0eddee8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..d26a29c80583 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..4a552b0aed6a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark : + SwitchBackToSplitFromHome(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b7376eaea66d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark : + SwitchBackToSplitFromHome(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..b2d05e4a2632 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark : + SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..6de31b1315e4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark : + SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..aab18a6d27b9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +@RequiresDevice +class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark : + SwitchBetweenSplitPairs(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b074f2c161c9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +@RequiresDevice +class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark : + SwitchBetweenSplitPairs(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..c402aa4444d8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RequiresDevice +@RunWith(BlockJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..840401c23a91 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RequiresDevice +@RunWith(BlockJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt new file mode 100644 index 000000000000..a5c512267508 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { + @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt new file mode 100644 index 000000000000..092fb6720e57 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { + @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt new file mode 100644 index 000000000000..8cb25fe531b8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByDividerGesturalNavLandscape : + DismissSplitScreenByDivider(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt new file mode 100644 index 000000000000..fa1be63296e0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByDividerGesturalNavPortrait : + DismissSplitScreenByDivider(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt new file mode 100644 index 000000000000..aa35237b615f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByGoHomeGesturalNavLandscape : + DismissSplitScreenByGoHome(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt new file mode 100644 index 000000000000..e195360cdc36 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DismissSplitScreenByGoHomeGesturalNavPortrait : + DismissSplitScreenByGoHome(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt new file mode 100644 index 000000000000..c1b3aade11cd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"]) + @Test + override fun dragDividerToResize() = super.dragDividerToResize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt new file mode 100644 index 000000000000..c6e2e854ab89 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"]) + @Test + override fun dragDividerToResize() = super.dragDividerToResize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt new file mode 100644 index 000000000000..5f771c7545c7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt new file mode 100644 index 000000000000..729a401fe34c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt new file mode 100644 index 000000000000..6e4cf9f55cfd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt new file mode 100644 index 000000000000..cc2870213e8d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt new file mode 100644 index 000000000000..736604f02377 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt new file mode 100644 index 000000000000..8df8dfaab071 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt new file mode 100644 index 000000000000..378f055ef830 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt new file mode 100644 index 000000000000..b33d26288d33 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt new file mode 100644 index 000000000000..b1d3858b9dbb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenFromOverviewGesturalNavLandscape : + EnterSplitScreenFromOverview(Rotation.ROTATION_90) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt new file mode 100644 index 000000000000..6d824c74c1fe --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class EnterSplitScreenFromOverviewGesturalNavPortrait : + EnterSplitScreenFromOverview(Rotation.ROTATION_0) { + + @ExpectedScenarios(["SPLIT_SCREEN_ENTER"]) + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt new file mode 100644 index 000000000000..f1d3d0cf2716 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchAppByDoubleTapDividerGesturalNavLandscape : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { + + @ExpectedScenarios([]) + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt new file mode 100644 index 000000000000..a867bac8c0eb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchAppByDoubleTapDividerGesturalNavPortrait : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { + + @ExpectedScenarios([]) + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt new file mode 100644 index 000000000000..76247ba10037 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt new file mode 100644 index 000000000000..e179da81e4db --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt new file mode 100644 index 000000000000..20f554f7d154 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromHomeGesturalNavLandscape : + SwitchBackToSplitFromHome(Rotation.ROTATION_90) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt new file mode 100644 index 000000000000..f7776ee3244a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromHomeGesturalNavPortrait : + SwitchBackToSplitFromHome(Rotation.ROTATION_0) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt new file mode 100644 index 000000000000..00f607343b3b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromRecentGesturalNavLandscape : + SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt new file mode 100644 index 000000000000..b3340e77dbfa --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBackToSplitFromRecentGesturalNavPortrait : + SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt new file mode 100644 index 000000000000..3da61e5e310c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt new file mode 100644 index 000000000000..627ae1843314 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt new file mode 100644 index 000000000000..7cbc1c3c272c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt new file mode 100644 index 000000000000..2eb81e0d0492 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.flicker + +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() { + + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt new file mode 100644 index 000000000000..76ad6b9bc49c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CopyContentInSplit +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val textEditApp = SplitScreenUtils.getIme(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) + } + + @Test + open fun copyContentInSplit() { + SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt new file mode 100644 index 000000000000..25182b40a300 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByDivider() { + if (tapl.isTablet) { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = false, + dragToBottom = true + ) + } else { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = true, + dragToBottom = true + ) + } + wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt new file mode 100644 index 000000000000..000b628b5ff6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByGoHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByGoHome() { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt new file mode 100644 index 000000000000..dd9ff3c7f64f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DragDividerToResize +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dragDividerToResize() { + SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt new file mode 100644 index 000000000000..4bbb9aa07911 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromAllApps +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromAllApps() { + tapl.launchedAppState.taskbar + .openAllApps() + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt new file mode 100644 index 000000000000..a2b75267b662 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromNotification +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + // Send a notification + sendNotificationApp.launchViaIntent(wmHelper) + sendNotificationApp.postNotification(wmHelper) + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromNotification() { + SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + sendNotificationApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt new file mode 100644 index 000000000000..1ccd8133c234 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromShortcut +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + Assume.assumeTrue(tapl.isTablet) + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromShortcut() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .openDeepShortcutMenu() + .getMenuItem("Split Screen Secondary Activity") + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + + // TODO: Do we want this check in here? Add to the other tests? + // flicker.splitScreenEntered( + // primaryApp, + // secondaryApp, + // fromOtherApp = false, + // appExistAtStart = false + // ) + } + + @After + fun teardwon() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt new file mode 100644 index 000000000000..664786b9e523 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromTaskbar +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromTaskbar() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt new file mode 100644 index 000000000000..88fd0841b174 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenFromOverview +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + + @Test + open fun enterSplitScreenFromOverview() { + SplitScreenUtils.splitFromOverview(tapl, device) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt new file mode 100644 index 000000000000..83a18e8d0b49 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.graphics.Point +import android.os.SystemClock +import android.platform.test.rule.NavigationModeRule +import android.platform.test.rule.PressHomeRule +import android.platform.test.rule.UnlockScreenRule +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.IComponentMatcher +import android.tools.common.traces.component.IComponentNameMatcher +import android.tools.device.apphelpers.MessagingAppHelper +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import org.junit.Assert.assertNotNull +import org.junit.rules.RuleChain + +object SplitScreenUtils { + private const val TIMEOUT_MS = 3_000L + private const val DRAG_DURATION_MS = 1_000L + private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" + private const val DIVIDER_BAR = "docked_divider_handle" + private const val OVERVIEW_SNAPSHOT = "snapshot" + private const val GESTURE_STEP_MS = 16L + private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L + private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + + private val notificationScrollerSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) + private val notificationContentSelector: BySelector + get() = By.text("Flicker Test Notification") + private val dividerBarSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) + private val overviewSnapshotSelector: BySelector + get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT) + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + fun testSetupRule(navigationMode: () -> NavBar, rotation: () -> Rotation): RuleChain { + return RuleChain.outerRule(UnlockScreenRule()) + .around( + NavigationModeRule( + navigationMode().value, + /* changeNavigationModeAfterTest */ false + ) + ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) + .around(RemoveAllTasksButHomeRule()) + .around( + ChangeDisplayOrientationRule( + rotation(), + resetOrientationAfterTest = false, + clearCacheAfterParsing = false + ) + ) + .around(PressHomeRule()) + } + + fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) + + fun getSecondary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Secondary.LABEL, + ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent() + ) + + fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper = + NonResizeableAppHelper(instrumentation) + + fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper = + NotificationAppHelper(instrumentation) + + fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation) + + fun waitForSplitComplete( + wmHelper: WindowManagerStateHelper, + primaryApp: IComponentMatcher, + secondaryApp: IComponentMatcher, + ) { + wmHelper + .StateSyncBuilder() + .withWindowSurfaceAppeared(primaryApp) + .withWindowSurfaceAppeared(secondaryApp) + .withSplitDividerVisible() + .waitForAndVerify() + } + + fun enterSplit( + wmHelper: WindowManagerStateHelper, + tapl: LauncherInstrumentation, + device: UiDevice, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + splitFromOverview(tapl, device) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) { + // Note: The initial split position in landscape is different between tablet and phone. + // In landscape, tablet will let the first app split to right side, and phone will + // split to left side. + if (tapl.isTablet) { + // TAPL's currentTask on tablet is sometimes not what we expected if the overview + // contains more than 3 task views. We need to use uiautomator directly to find the + // second task to split. + tapl.workspace.switchToOverview().overviewActions.clickSplit() + val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) + if (snapshots == null || snapshots.size < 1) { + error("Fail to find a overview snapshot to split.") + } + + // Find the second task in the upper right corner in split select mode by sorting + // 'left' in descending order and 'top' in ascending order. + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t2.getVisibleBounds().left - t1.getVisibleBounds().left + } + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t1.getVisibleBounds().top - t2.getVisibleBounds().top + } + snapshots[0].click() + } else { + tapl.workspace + .switchToOverview() + .currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() + } + SystemClock.sleep(TIMEOUT_MS) + } + + fun enterSplitViaIntent( + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + val stringExtras = + mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true") + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun dragFromNotificationToSplit( + instrumentation: Instrumentation, + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + + // Pull down the notifications + device.swipe( + displayBounds.centerX(), + 5, + displayBounds.centerX(), + displayBounds.bottom, + 50 /* steps */ + ) + SystemClock.sleep(TIMEOUT_MS) + + // Find the target notification + val notificationScroller = + device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS) + ?: error("Unable to find view $notificationScrollerSelector") + var notificationContent = notificationScroller.findObject(notificationContentSelector) + + while (notificationContent == null) { + device.swipe( + displayBounds.centerX(), + displayBounds.centerY(), + displayBounds.centerX(), + displayBounds.centerY() - 150, + 20 /* steps */ + ) + notificationContent = notificationScroller.findObject(notificationContentSelector) + } + + // Drag to split + val dragStart = notificationContent.visibleCenter + val dragMiddle = Point(dragStart.x + 50, dragStart.y) + val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val downTime = SystemClock.uptimeMillis() + + touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart) + // It needs a horizontal movement to trigger the drag + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragStart, + dragMiddle + ) + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragMiddle, + dragEnd + ) + // Wait for a while to start splitting + SystemClock.sleep(TIMEOUT_MS) + touch( + instrumentation, + MotionEvent.ACTION_UP, + downTime, + SystemClock.uptimeMillis(), + GESTURE_STEP_MS, + dragEnd + ) + SystemClock.sleep(TIMEOUT_MS) + } + + fun touch( + instrumentation: Instrumentation, + action: Int, + downTime: Long, + eventTime: Long, + duration: Long, + point: Point + ) { + val motionEvent = + MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionEvent, true) + motionEvent.recycle() + SystemClock.sleep(duration) + } + + fun touchMove( + instrumentation: Instrumentation, + downTime: Long, + eventTime: Long, + duration: Long, + from: Point, + to: Point + ) { + val steps: Long = duration / GESTURE_STEP_MS + var currentTime = eventTime + var currentX = from.x.toFloat() + var currentY = from.y.toFloat() + val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() + val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() + + for (i in 1..steps) { + val motionMove = + MotionEvent.obtain( + downTime, + currentTime, + MotionEvent.ACTION_MOVE, + currentX, + currentY, + 0 + ) + motionMove.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionMove, true) + motionMove.recycle() + + currentTime += GESTURE_STEP_MS + if (i == steps - 1) { + currentX = to.x.toFloat() + currentY = to.y.toFloat() + } else { + currentX += stepX + currentY += stepY + } + SystemClock.sleep(GESTURE_STEP_MS) + } + } + + fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) { + tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) + val allApps = tapl.workspace.switchToAllApps() + allApps.freeze() + try { + allApps.getAppIcon(appName).dragToHotseat(0) + } finally { + allApps.unfreeze() + } + } + + fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200) + + wmHelper + .StateSyncBuilder() + .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) + .waitForAndVerify() + } + + fun dragDividerToDismissSplit( + device: UiDevice, + wmHelper: WindowManagerStateHelper, + dragToRight: Boolean, + dragToBottom: Boolean + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag( + Point( + if (dragToRight) { + displayBounds.width * 4 / 5 + } else { + displayBounds.width * 1 / 5 + }, + if (dragToBottom) { + displayBounds.height * 4 / 5 + } else { + displayBounds.height * 1 / 5 + } + ) + ) + } + + fun doubleTapDividerToSwitch(device: UiDevice) { + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + val interval = + (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2 + dividerBar.click() + SystemClock.sleep(interval.toLong()) + dividerBar.click() + } + + fun copyContentInSplit( + instrumentation: Instrumentation, + device: UiDevice, + sourceApp: IComponentNameMatcher, + destinationApp: IComponentNameMatcher, + ) { + // Copy text from sourceApp + val textView = + device.wait( + Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the TextView", textView) + textView.click(LONG_PRESS_TIME_MS) + + val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) + assertNotNull("Unable to find the copy button", copyBtn) + copyBtn.click() + + // Paste text to destinationApp + val editText = + device.wait( + Until.findObject(By.res(destinationApp.packageName, "plain_text_input")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the EditText", editText) + editText.click(LONG_PRESS_TIME_MS) + + val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) + assertNotNull("Unable to find the paste button", pasteBtn) + pasteBtn.click() + + // Verify text + if (!textView.text.contentEquals(editText.text)) { + error("Fail to copy content in split") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt new file mode 100644 index 000000000000..e5501f4c6cd2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.graphics.Point +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.WindowUtils +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchAppByDoubleTapDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.workspace.switchToOverview().dismissAllTasks() + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun switchAppByDoubleTapDivider() { + SplitScreenUtils.doubleTapDividerToSwitch(device) + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + + waitForLayersToSwitch(wmHelper) + waitForWindowsToSwitch(wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } + + private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appWindowsSwitched") { + val primaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + primaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + secondaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + + if (isLandscape(rotation)) { + return@add if (isTablet()) { + secondaryAppWindow.frame.right <= primaryAppWindow.frame.left + } else { + primaryAppWindow.frame.right <= secondaryAppWindow.frame.left + } + } else { + return@add if (isTablet()) { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } else { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } + } + } + .waitForAndVerify() + } + + private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appLayersSwitched") { + val primaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + primaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + secondaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + + val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false + val secondaryVisibleRegion = + secondaryAppLayer.visibleRegion?.bounds ?: return@add false + + if (isLandscape(rotation)) { + return@add if (isTablet()) { + secondaryVisibleRegion.right <= primaryVisibleRegion.left + } else { + primaryVisibleRegion.right <= secondaryVisibleRegion.left + } + } else { + return@add if (isTablet()) { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } else { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } + } + } + .waitForAndVerify() + } + + private fun isLandscape(rotation: Rotation): Boolean { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return displayBounds.width > displayBounds.height + } + + private fun isTablet(): Boolean { + val sizeDp: Point = device.displaySizeDp + val LARGE_SCREEN_DP_THRESHOLD = 600 + return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt new file mode 100644 index 000000000000..b3f1e87380e4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromAnotherApp +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + thirdApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromAnotherApp() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt new file mode 100644 index 000000000000..d1121162c267 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromHome() { + tapl.workspace.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt new file mode 100644 index 000000000000..9ab924ca46f6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromRecent +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromRecent() { + tapl.workspace.switchToOverview().currentTask.open() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt new file mode 100644 index 000000000000..b694dfa7f384 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBetweenSplitPairs +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getIme(instrumentation) + private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp) + } + + @Test + open fun switchBetweenSplitPairs() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + thirdApp.exit(wmHelper) + fourthApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt new file mode 100644 index 000000000000..f78b7881735a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class UnlockKeyguardToSplitScreen { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = + SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { Rotation.ROTATION_0 }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(Rotation.ROTATION_0.value) + + SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) + } + + @Test + open fun unlockKeyguardToSplitScreen() { + device.sleep() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + device.wakeUp() + device.pressMenu() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index a43ad9b4dd39..1d4c4d2f7068 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.EdgeExtensionComponentMatcher @@ -28,13 +27,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -60,21 +55,6 @@ class CopyContentInSplit(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(textEditApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(textEditApp) - flicker.splitScreenDividerIsVisibleAtEnd() - - // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index a118c08b35e2..0d967eb15e0f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -26,13 +25,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsChanges -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -58,19 +53,6 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(secondaryApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index 580b153421a4..d3434a5b18e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -21,6 +21,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseBenchmarkTest +import com.android.wm.shell.flicker.SplitScreenUtils abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) { protected val context: Context = instrumentation.context diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index e0a47b394ba1..f236c2d11ecb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar @@ -28,12 +27,9 @@ import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -59,19 +55,6 @@ class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(secondaryApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 8f867df3fea1..8aaa98a5ca9f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -28,15 +27,10 @@ import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.appWindowBecomesVisible -import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.layerBecomesInvisible import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -62,21 +56,6 @@ class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(thirdApp) - flicker.appWindowIsVisibleAtStart(fourthApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.appWindowIsInvisibleAtEnd(thirdApp) - flicker.appWindowIsInvisibleAtEnd(fourthApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerInvisibleAtMiddle() = diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index d1ca9eac198d..d9d22def6992 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -16,18 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) : +abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val textEditApp = SplitScreenUtils.getIme(instrumentation) protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#") @@ -54,21 +51,6 @@ open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit() - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 73acb1f0cc47..7e8d60b441bb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenDismissed +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) : +abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -61,19 +57,6 @@ open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlic } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index 86ffd2af6748..770e0328c4f6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenDismissed +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) : +abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -47,19 +43,6 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlick } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index dfde3b669813..46570fde1942 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -16,19 +16,16 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +34,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) : +abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -45,27 +42,11 @@ open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - @Before fun before() { Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is - // robust enough to get the correct end state. - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index d13e4134a961..5c3d4ffa8663 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) : +abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit @@ -57,30 +53,11 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Lega } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - @Before fun before() { Assume.assumeTrue(tapl.isTablet) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index 1d4166922b13..6b122c686c58 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromNotificationBenchmark( +abstract class EnterSplitScreenByDragFromNotificationBenchmark( override val flicker: LegacyFlickerTest ) : SplitScreenBase(flicker) { protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) @@ -59,21 +55,6 @@ open class EnterSplitScreenByDragFromNotificationBenchmark( teardown { sendNotificationApp.exit(wmHelper) } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false) - @Before fun before() { Assume.assumeTrue(tapl.isTablet) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index b4bafa79cd48..78f9bab402e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,8 +35,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: LegacyFlickerTest) : - SplitScreenBase(flicker) { +abstract class EnterSplitScreenByDragFromShortcutBenchmark( + override val flicker: LegacyFlickerTest +) : SplitScreenBase(flicker) { @Before fun before() { Assume.assumeTrue(tapl.isTablet) @@ -62,25 +59,6 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: Leg } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index da44ecdb9304..78907f08edf3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) : +abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -56,26 +52,6 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Lega } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - @Before fun before() { Assume.assumeTrue(tapl.isTablet) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index af06d6da2518..2c91e84a01fe 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) : +abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -56,19 +52,6 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFli } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 23156b5d1628..fa09c2ee4e21 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -16,8 +16,6 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -27,10 +25,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +36,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,14 +50,6 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlic } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { wmHelper .StateSyncBuilder() @@ -134,14 +123,6 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlic return displayBounds.width > displayBounds.height } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is - // robust enough to get the correct end state. - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index 2d810d3e2631..ff220069b88e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -16,19 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) @@ -55,19 +51,6 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Legacy } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index f6df1e42d1b7..5787b02f8944 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -16,19 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,19 +49,6 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlicke } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index ba46bdcdad21..b2d50911f930 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -16,19 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,19 +49,6 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlic } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 0d871e500688..f234e462e63e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -16,17 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -35,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thirdApp = SplitScreenUtils.getIme(instrumentation) protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) @@ -64,9 +61,6 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerT thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit @Test open fun cujCompleted() {} - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt index 7952b7125a34..61c367933159 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt @@ -22,8 +22,8 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -33,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) : +abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -47,14 +47,6 @@ open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlic } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index df1e2e16f485..946a7ef7d8c3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -161,7 +161,7 @@ public final class StageTaskListenerTests extends ShellTestCase { childTask.supportsMultiWindow = false; mStageTaskListener.onTaskInfoChanged(childTask); - verify(mCallbacks).onNoLongerSupportMultiWindow(); + verify(mCallbacks).onNoLongerSupportMultiWindow(childTask); } @Test diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl index 2231ce14eea6..e46d34e81483 100644 --- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl +++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl @@ -17,9 +17,22 @@ package android.media.projection; import android.media.projection.MediaProjectionInfo; +import android.view.ContentRecordingSession; /** {@hide} */ oneway interface IMediaProjectionWatcherCallback { void onStart(in MediaProjectionInfo info); void onStop(in MediaProjectionInfo info); + /** + * Called when the {@link ContentRecordingSession} was set for the current media + * projection. + * + * @param info always present and contains information about the media projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + void onRecordingSessionSet( + in MediaProjectionInfo info, + in @nullable ContentRecordingSession session + ); } diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 5703c429c32b..5a68c53b8f68 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -29,6 +29,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; +import android.view.ContentRecordingSession; import android.view.Surface; import java.util.Map; @@ -300,7 +301,22 @@ public final class MediaProjectionManager { /** @hide */ public static abstract class Callback { public abstract void onStart(MediaProjectionInfo info); + public abstract void onStop(MediaProjectionInfo info); + + /** + * Called when the {@link ContentRecordingSession} was set for the current media + * projection. + * + * @param info always present and contains information about the media projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + public void onRecordingSessionSet( + @NonNull MediaProjectionInfo info, + @Nullable ContentRecordingSession session + ) { + } } /** @hide */ @@ -335,5 +351,13 @@ public final class MediaProjectionManager { } }); } + + @Override + public void onRecordingSessionSet( + @NonNull final MediaProjectionInfo info, + @Nullable final ContentRecordingSession session + ) { + mHandler.post(() -> mCallback.onRecordingSessionSet(info, session)); + } } } diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html index 917276b9ce65..ad18a9d64074 100644 --- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html +++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html @@ -81,5 +81,7 @@ Dismiss flow </button> <p id="dismiss_flow"></p> + + <h2>Test <a href="http://www.google.com">hyperlink</a></h2> </body> </html> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java index 2530257d629a..b1009808cccc 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java @@ -29,6 +29,7 @@ import android.util.Log; import android.view.KeyEvent; import android.webkit.CookieManager; import android.webkit.WebView; +import android.webkit.WebViewClient; import com.android.phone.slice.SlicePurchaseController; @@ -113,8 +114,10 @@ public class SlicePurchaseActivity extends Activity { return; } - // Create and configure WebView - setupWebView(); + // Clear any cookies that might be persisted from previous sessions before loading WebView + CookieManager.getInstance().removeAllCookies(value -> { + setupWebView(); + }); } protected void onPurchaseSuccessful() { @@ -176,12 +179,7 @@ public class SlicePurchaseActivity extends Activity { private void setupWebView() { // Create WebView mWebView = new WebView(this); - - // Clear any cookies and state that might be saved from previous sessions - CookieManager.getInstance().removeAllCookies(null); - CookieManager.getInstance().flush(); - mWebView.clearCache(true); - mWebView.clearHistory(); + mWebView.setWebViewClient(new WebViewClient()); // Enable JavaScript for the carrier purchase website to send results back to // the slice purchase application. diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 2e6bb535a8f0..f522fd13c9f8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -583,7 +583,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ public void setName(String name) { // Prevent getName() to be set to null if setName(null) is called - if (name == null || TextUtils.equals(name, getName())) { + if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) { return; } mDevice.setAlias(name); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index cd6609ec463e..963bd9daa975 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -15,6 +15,8 @@ */ package com.android.settingslib.media; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; @@ -22,6 +24,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -39,7 +42,13 @@ public class BluetoothMediaDevice extends MediaDevice { BluetoothMediaDevice(Context context, CachedBluetoothDevice device, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, device, routerManager, info, packageName, null); + } + + BluetoothMediaDevice(Context context, CachedBluetoothDevice device, + MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName, + RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mCachedDevice = device; mAudioManager = context.getSystemService(AudioManager.class); initDeviceRecord(); @@ -58,6 +67,12 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public Drawable getIcon() { return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice()) ? mContext.getDrawable(R.drawable.ic_earbuds_advanced) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 2a486a9a613b..1728e405fa29 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -568,8 +568,10 @@ public class InfoMediaManager extends MediaManager { case TYPE_HDMI: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: - mediaDevice = - new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName); + mediaDevice = mPreferenceItemMap.containsKey(route.getId()) ? new PhoneMediaDevice( + mContext, mRouterManager, route, mPackageName, + mPreferenceItemMap.get(route.getId())) : new PhoneMediaDevice(mContext, + mRouterManager, route, mPackageName); break; case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: @@ -579,8 +581,11 @@ public class InfoMediaManager extends MediaManager { final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { - mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, - route, mPackageName); + mediaDevice = mPreferenceItemMap.containsKey(route.getId()) + ? new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName, mPreferenceItemMap.get(route.getId())) + : new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName); } break; case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER: diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 34519c993d27..accd88c2bfe3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -24,10 +24,13 @@ import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import androidx.annotation.VisibleForTesting; @@ -51,7 +54,12 @@ public class PhoneMediaDevice extends MediaDevice { PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, routerManager, info, packageName, null); + } + + PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, + String packageName, RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mDeviceIconUtil = new DeviceIconUtil(); initDeviceRecord(); } @@ -86,6 +94,12 @@ public class PhoneMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public String getSummary() { return mSummary; } diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index 5c55a435b463..c037c400948f 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -42,7 +42,10 @@ android_robolectric_test { name: "SettingsLibRoboTests", srcs: ["src/**/*.java"], static_libs: [ + "Settings_robolectric_meta_service_file", + "Robolectric_shadows_androidx_fragment_upstream", "SettingsLib-robo-testutils", + "androidx.fragment_fragment", "androidx.test.core", "androidx.core_core", "testng", // TODO: remove once JUnit on Android provides assertThrows @@ -53,6 +56,20 @@ android_robolectric_test { test_options: { timeout: 36000, }, + upstream: true, +} + +java_genrule { + name: "Settings_robolectric_meta_service_file", + out: ["robolectric_meta_service_file.jar"], + tools: ["soong_zip"], + cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" + + "echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + + "echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + + "echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + + //"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + + "echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + + "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/", } java_library { @@ -60,9 +77,23 @@ java_library { srcs: [ "testutils/com/android/settingslib/testutils/**/*.java", ], - + javacflags: [ + "-Aorg.robolectric.annotation.processing.shadowPackage=com.android.settingslib.testutils.shadow", + "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR", + // Uncomment the below to debug annotation processors not firing. + //"-verbose", + //"-XprintRounds", + //"-XprintProcessorInfo", + //"-Xlint", + //"-J-verbose", + ], + plugins: [ + "auto_value_plugin_1.9", + "auto_value_builder_plugin_1.9", + "Robolectric_processor_upstream", + ], libs: [ - "Robolectric_all-target", + "Robolectric_all-target_upstream", "mockito-robolectric-prebuilt", "truth-prebuilt", ], diff --git a/packages/SettingsLib/tests/robotests/config/robolectric.properties b/packages/SettingsLib/tests/robotests/config/robolectric.properties index fab7251d020b..2a9e50df62b0 100644 --- a/packages/SettingsLib/tests/robotests/config/robolectric.properties +++ b/packages/SettingsLib/tests/robotests/config/robolectric.properties @@ -1 +1,2 @@ sdk=NEWEST_SDK +instrumentedPackages=androidx.preference diff --git a/packages/SettingsLib/tests/robotests/fragment/Android.bp b/packages/SettingsLib/tests/robotests/fragment/Android.bp new file mode 100644 index 000000000000..3e67156af0c4 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/Android.bp @@ -0,0 +1,40 @@ +//############################################# +// Compile Robolectric shadows framework misapplied to androidx +//############################################# + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library { + name: "Robolectric_shadows_androidx_fragment_upstream", + srcs: [ + "src/main/java/**/*.java", + "src/main/java/**/*.kt", + ], + javacflags: [ + "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment", + "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR", + // Uncomment the below to debug annotation processors not firing. + //"-verbose", + //"-XprintRounds", + //"-XprintProcessorInfo", + //"-Xlint", + //"-J-verbose", + ], + libs: [ + "Robolectric_all-target_upstream", + "androidx.fragment_fragment", + ], + plugins: [ + "auto_value_plugin_1.9", + "auto_value_builder_plugin_1.9", + "Robolectric_processor_upstream", + ], + +} diff --git a/packages/SettingsLib/tests/robotests/fragment/BUILD b/packages/SettingsLib/tests/robotests/fragment/BUILD new file mode 100644 index 000000000000..393a02e8464c --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/BUILD @@ -0,0 +1,69 @@ +load("//third_party/java/android/android_sdk_linux/extras/android/compatibility/jetify:jetify.bzl", "jetify_android_library", "jetify_android_local_test") + +package( + default_applicable_licenses = ["//third_party/java_src/robolectric:license"], + default_visibility = ["//third_party/java_src/robolectric:__subpackages__"], +) + +licenses(["notice"]) + +#============================================================================== +# Test resources library +#============================================================================== +jetify_android_library( + name = "test_resources", + custom_package = "org.robolectric.shadows.androidx.fragment", + manifest = "src/test/AndroidManifest.xml", + resource_files = glob( + ["src/test/resources/**/*"], + ), +) + +#============================================================================== +# AndroidX fragment module library +#============================================================================== +jetify_android_library( + name = "androidx_fragment", + testonly = 1, + srcs = glob( + ["src/main/java/**"], + ), + custom_package = "org.robolectric.shadows.androidx.fragment", + javacopts = [ + "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.androidx.fragment", + ], + jetify_sources = True, + plugins = [ + "//java/com/google/thirdparty/robolectric/processor", + ], + deps = [ + "//third_party/java/androidx/core", + "//third_party/java/androidx/fragment", + "//third_party/java/androidx/lifecycle", + "//third_party/java_src/robolectric/shadowapi", + "//third_party/java_src/robolectric/shadows/framework", + ], +) + +[ + jetify_android_local_test( + name = "test_" + src.rstrip(".java"), + size = "small", + srcs = glob( + ["src/test/java/**/*.java"], + ), + jetify_sources = True, + deps = [ + ":androidx_fragment", + ":test_resources", + "//third_party/java/androidx/fragment", + "//third_party/java/androidx/loader", + "//third_party/java/mockito", + "//third_party/java/robolectric", + "//third_party/java/truth", + ], + ) + for src in glob( + ["src/test/java/**/*Test.java"], + ) +] diff --git a/packages/SettingsLib/tests/robotests/fragment/build.gradle b/packages/SettingsLib/tests/robotests/fragment/build.gradle new file mode 100644 index 000000000000..d9dcd84ded89 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/build.gradle @@ -0,0 +1,48 @@ +plugins { + id "net.ltgt.errorprone" version "0.0.13" +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + android { + sourceSets { + main { + res.srcDirs = ['src/test/resources/res'] + } + } + testOptions { + unitTests { + includeAndroidResources = true + } + } + } +} + +dependencies { + // Project dependencies + compileOnly project(":robolectric") + + // Compile dependencies + compileOnly AndroidSdk.MAX_SDK.coordinates + compileOnly "androidx.core:core:1.0.0-rc02" + compileOnly 'androidx.fragment:fragment:1.0.0-rc02' + compileOnly "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01" + compileOnly "androidx.lifecycle:lifecycle-common:2.0.0-beta01" + + // Testing dependencies + testImplementation "com.google.truth:truth:0.44" + testImplementation "org.mockito:mockito-core:2.5.4" + testImplementation "androidx.arch.core:core-common:2.0.0-beta01" + testImplementation "androidx.arch.core:core-runtime:2.0.0-rc01" + testImplementation "androidx.collection:collection:1.0.0-rc01" + testImplementation "androidx.core:core:1.0.0-rc02" + testImplementation 'androidx.fragment:fragment:1.0.0-rc02' + testImplementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0-rc01" + testImplementation "androidx.lifecycle:lifecycle-common:2.0.0-beta01" + testImplementation "androidx.lifecycle:lifecycle-runtime:2.0.0-rc01" + testImplementation "androidx.lifecycle:lifecycle-livedata-core:2.0.0-rc01" + testImplementation "androidx.loader:loader:1.0.0-rc02" +} diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java new file mode 100644 index 000000000000..c688683e7f8a --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/FragmentController.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.robolectric.shadows.androidx.fragment; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.LinearLayout; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import org.robolectric.android.controller.ActivityController; +import org.robolectric.android.controller.ComponentController; +import org.robolectric.util.ReflectionHelpers; + +/** A Controller that can be used to drive the lifecycle of a {@link Fragment} */ +public class FragmentController<F extends Fragment> + extends ComponentController<FragmentController<F>, F> { + + private final F mFragment; + private final ActivityController<? extends FragmentActivity> mActivityController; + + private FragmentController(F fragment, Class<? extends FragmentActivity> activityClass) { + this(fragment, activityClass, null /*intent*/, null /*arguments*/); + } + + private FragmentController( + F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) { + this(fragment, activityClass, intent, null /*arguments*/); + } + + private FragmentController( + F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) { + this(fragment, activityClass, null /*intent*/, arguments); + } + + private FragmentController( + F fragment, + Class<? extends FragmentActivity> activityClass, + Intent intent, + Bundle arguments) { + super(fragment, intent); + this.mFragment = fragment; + if (arguments != null) { + this.mFragment.setArguments(arguments); + } + this.mActivityController = + ActivityController.of(ReflectionHelpers.callConstructor(activityClass), intent); + } + + /** + * Generate the {@link FragmentController} for specific fragment. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of(F fragment) { + return new FragmentController<>(fragment, FragmentControllerActivity.class); + } + + /** + * Generate the {@link FragmentController} for specific fragment and intent. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @param intent the intent which will be retained by activity + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of(F fragment, Intent intent) { + return new FragmentController<>(fragment, FragmentControllerActivity.class, intent); + } + + /** + * Generate the {@link FragmentController} for specific fragment and arguments. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @param arguments the arguments which will be retained by fragment + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of(F fragment, Bundle arguments) { + return new FragmentController<>(fragment, FragmentControllerActivity.class, arguments); + } + + /** + * Generate the {@link FragmentController} for specific fragment and activity class. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @param activityClass the activity which will be attached by fragment + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of( + F fragment, Class<? extends FragmentActivity> activityClass) { + return new FragmentController<>(fragment, activityClass); + } + + /** + * Generate the {@link FragmentController} for specific fragment, intent and arguments. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @param intent the intent which will be retained by activity + * @param arguments the arguments which will be retained by fragment + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of( + F fragment, Intent intent, Bundle arguments) { + return new FragmentController<>(fragment, FragmentControllerActivity.class, intent, + arguments); + } + + /** + * Generate the {@link FragmentController} for specific fragment, activity class and intent. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @param activityClass the activity which will be attached by fragment + * @param intent the intent which will be retained by activity + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of( + F fragment, Class<? extends FragmentActivity> activityClass, Intent intent) { + return new FragmentController<>(fragment, activityClass, intent); + } + + /** + * Generate the {@link FragmentController} for specific fragment, activity class and arguments. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @param activityClass the activity which will be attached by fragment + * @param arguments the arguments which will be retained by fragment + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of( + F fragment, Class<? extends FragmentActivity> activityClass, Bundle arguments) { + return new FragmentController<>(fragment, activityClass, arguments); + } + + /** + * Generate the {@link FragmentController} for specific fragment, activity class, intent and + * arguments. + * + * @param fragment the fragment which you'd like to drive lifecycle + * @param activityClass the activity which will be attached by fragment + * @param intent the intent which will be retained by activity + * @param arguments the arguments which will be retained by fragment + * @return {@link FragmentController} + */ + public static <F extends Fragment> FragmentController<F> of( + F fragment, + Class<? extends FragmentActivity> activityClass, + Intent intent, + Bundle arguments) { + return new FragmentController<>(fragment, activityClass, intent, arguments); + } + + /** + * Sets up the given fragment by attaching it to an activity, calling its onCreate() through + * onResume() lifecycle methods, and then making it visible. Note that the fragment will be + * added + * to the view with ID 1. + */ + public static <F extends Fragment> F setupFragment(F fragment) { + return FragmentController.of(fragment).create().start().resume().visible().get(); + } + + /** + * Sets up the given fragment by attaching it to an activity, calling its onCreate() through + * onResume() lifecycle methods, and then making it visible. Note that the fragment will be + * added + * to the view with ID 1. + */ + public static <F extends Fragment> F setupFragment( + F fragment, Class<? extends FragmentActivity> fragmentActivityClass) { + return FragmentController.of(fragment, fragmentActivityClass) + .create() + .start() + .resume() + .visible() + .get(); + } + + /** + * Sets up the given fragment by attaching it to an activity created with the given bundle, + * calling its onCreate() through onResume() lifecycle methods, and then making it visible. Note + * that the fragment will be added to the view with ID 1. + */ + public static <F extends Fragment> F setupFragment( + F fragment, Class<? extends FragmentActivity> fragmentActivityClass, Bundle bundle) { + return FragmentController.of(fragment, fragmentActivityClass) + .create(bundle) + .start() + .resume() + .visible() + .get(); + } + + /** + * Sets up the given fragment by attaching it to an activity created with the given bundle and + * container id, calling its onCreate() through onResume() lifecycle methods, and then making it + * visible. + */ + public static <F extends Fragment> F setupFragment( + F fragment, + Class<? extends FragmentActivity> fragmentActivityClass, + int containerViewId, + Bundle bundle) { + return FragmentController.of(fragment, fragmentActivityClass) + .create(containerViewId, bundle) + .start() + .resume() + .visible() + .get(); + } + + /** + * Creates the activity with {@link Bundle} and adds the fragment to the view with ID {@code + * contentViewId}. + */ + public FragmentController<F> create(final int contentViewId, final Bundle bundle) { + shadowMainLooper.runPaused( + new Runnable() { + @Override + public void run() { + mActivityController + .create(bundle) + .get() + .getSupportFragmentManager() + .beginTransaction() + .add(contentViewId, mFragment) + .commit(); + } + }); + return this; + } + + /** + * Creates the activity with {@link Bundle} and adds the fragment to it. Note that the fragment + * will be added to the view with ID 1. + */ + public FragmentController<F> create(final Bundle bundle) { + return create(1, bundle); + } + + /** + * Creates the {@link Fragment} in a newly initialized state and hence will receive a null + * savedInstanceState {@link Bundle parameter} + */ + @Override + public FragmentController<F> create() { + return create(null); + } + + /** Drive lifecycle of activity to Start lifetime */ + public FragmentController<F> start() { + shadowMainLooper.runPaused( + new Runnable() { + @Override + public void run() { + mActivityController.start(); + } + }); + return this; + } + + /** Drive lifecycle of activity to Resume lifetime */ + public FragmentController<F> resume() { + shadowMainLooper.runPaused( + new Runnable() { + @Override + public void run() { + mActivityController.resume(); + } + }); + return this; + } + + /** Drive lifecycle of activity to Pause lifetime */ + public FragmentController<F> pause() { + shadowMainLooper.runPaused( + new Runnable() { + @Override + public void run() { + mActivityController.pause(); + } + }); + return this; + } + + /** Drive lifecycle of activity to Stop lifetime */ + public FragmentController<F> stop() { + shadowMainLooper.runPaused( + new Runnable() { + @Override + public void run() { + mActivityController.stop(); + } + }); + return this; + } + + /** Drive lifecycle of activity to Destroy lifetime */ + @Override + public FragmentController<F> destroy() { + shadowMainLooper.runPaused( + new Runnable() { + @Override + public void run() { + mActivityController.destroy(); + } + }); + return this; + } + + /** Let activity can be visible lifetime */ + public FragmentController<F> visible() { + shadowMainLooper.runPaused( + new Runnable() { + @Override + public void run() { + mActivityController.visible(); + } + }); + return this; + } + + private static class FragmentControllerActivity extends FragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + LinearLayout view = new LinearLayout(this); + view.setId(1); + + setContentView(view); + } + } +} diff --git a/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java new file mode 100644 index 000000000000..dd89441255c6 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/src/main/java/org/robolectric/shadows/androidx/fragment/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Testing infrastructure for androidx.fragment library. + * + * <p>To use this in your project, add the artifact {@code + * org.robolectric:shadows-androidx-fragment} to your project. + */ +package org.robolectric.shadows.androidx.fragment; diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml new file mode 100644 index 000000000000..8493c0296c8b --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/src/test/AndroidManifest.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.robolectric.shadows.androidx.fragment"> + + <uses-sdk android:targetSdkVersion="28"/> +</manifest> diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java new file mode 100644 index 000000000000..ef6305869b1f --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/src/test/java/org/robolectric/shadows/androidx/fragment/FragmentControllerTest.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.robolectric.shadows.androidx.fragment; + +import static android.os.Looper.getMainLooper; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.Shadows.shadowOf; + +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; +import java.util.List; + +/** Tests for {@link FragmentController} */ +@RunWith(RobolectricTestRunner.class) +public class FragmentControllerTest { + + @After + public void tearDown() { + TranscriptFragment.clearLifecycleEvents(); + } + + @Test + public void initialNotAttached() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment()); + + assertThat(controller.get().getView()).isNull(); + assertThat(controller.get().getActivity()).isNull(); + assertThat(controller.get().isAdded()).isFalse(); + } + + @Test + public void initialNotAttached_customActivity() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class); + + assertThat(controller.get().getView()).isNull(); + assertThat(controller.get().getActivity()).isNull(); + assertThat(controller.get().isAdded()).isFalse(); + } + + @Test + public void attachedAfterCreate() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment()); + + controller.create(); + shadowOf(getMainLooper()).idle(); + + assertThat(controller.get().getActivity()).isNotNull(); + assertThat(controller.get().isAdded()).isTrue(); + assertThat(controller.get().isResumed()).isFalse(); + } + + @Test + public void attachedAfterCreate_customActivity() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class); + + controller.create(); + shadowOf(getMainLooper()).idle(); + + assertThat(controller.get().getActivity()).isNotNull(); + assertThat(controller.get().getActivity()).isInstanceOf(TestActivity.class); + assertThat(controller.get().isAdded()).isTrue(); + assertThat(controller.get().isResumed()).isFalse(); + } + + @Test + public void attachedAfterCreate_customizedViewId() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), CustomizedViewIdTestActivity.class); + + controller.create(R.id.custom_activity_view, null).start(); + + assertThat(controller.get().getView()).isNotNull(); + assertThat(controller.get().getActivity()).isNotNull(); + assertThat(controller.get().isAdded()).isTrue(); + assertThat(controller.get().isResumed()).isFalse(); + assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull(); + } + + @Test + public void hasViewAfterStart() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment()); + + controller.create().start(); + + assertThat(controller.get().getView()).isNotNull(); + } + + @Test + public void isResumed() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class); + + controller.create().start().resume(); + + assertThat(controller.get().getView()).isNotNull(); + assertThat(controller.get().getActivity()).isNotNull(); + assertThat(controller.get().isAdded()).isTrue(); + assertThat(controller.get().isResumed()).isTrue(); + assertThat((TextView) controller.get().getView().findViewById(R.id.tacos)).isNotNull(); + } + + @Test + public void isPaused() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class); + + controller.create().start().resume().pause(); + + assertThat(controller.get().getView()).isNotNull(); + assertThat(controller.get().getActivity()).isNotNull(); + assertThat(controller.get().isAdded()).isTrue(); + assertThat(controller.get().isResumed()).isFalse(); + assertThat(controller.get().getLifecycleEvents()) + .containsExactly("onCreate", "onStart", "onResume", "onPause") + .inOrder(); + } + + @Test + public void isStopped() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class); + + controller.create().start().resume().pause().stop(); + + assertThat(controller.get().getView()).isNotNull(); + assertThat(controller.get().getActivity()).isNotNull(); + assertThat(controller.get().isAdded()).isTrue(); + assertThat(controller.get().isResumed()).isFalse(); + assertThat(controller.get().getLifecycleEvents()) + .containsExactly("onCreate", "onStart", "onResume", "onPause", "onStop") + .inOrder(); + } + + @Test + public void withIntent() { + final Intent intent = generateTestIntent(); + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class, intent); + + controller.create(); + shadowOf(getMainLooper()).idle(); + final Intent intentInFragment = controller.get().getActivity().getIntent(); + + assertThat(intentInFragment.getAction()).isEqualTo("test_action"); + assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value"); + } + + @Test + public void withArguments() { + final Bundle bundle = generateTestBundle(); + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class, bundle); + + controller.create(); + final Bundle args = controller.get().getArguments(); + + assertThat(args.getString("test_key")).isEqualTo("test_value"); + } + + @Test + public void withIntentAndArguments() { + final Bundle bundle = generateTestBundle(); + final Intent intent = generateTestIntent(); + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class, intent, bundle); + + controller.create(); + shadowOf(getMainLooper()).idle(); + final Intent intentInFragment = controller.get().getActivity().getIntent(); + final Bundle args = controller.get().getArguments(); + + assertThat(intentInFragment.getAction()).isEqualTo("test_action"); + assertThat(intentInFragment.getExtras().getString("test_key")).isEqualTo("test_value"); + assertThat(args.getString("test_key")).isEqualTo("test_value"); + } + + @Test + public void visible() { + final FragmentController<TranscriptFragment> controller = + FragmentController.of(new TranscriptFragment(), TestActivity.class); + + controller.create().start().resume(); + + assertThat(controller.get().isVisible()).isFalse(); + + controller.visible(); + + assertThat(controller.get().isVisible()).isTrue(); + } + + @Test + public void setupFragmentWithFragment_fragmentHasCorrectLifecycle() { + TranscriptFragment fragment = FragmentController.setupFragment(new TranscriptFragment()); + + assertThat(fragment.getLifecycleEvents()) + .containsExactly("onCreate", "onStart", "onResume") + .inOrder(); + assertThat(fragment.isVisible()).isTrue(); + } + + @Test + public void setupFragmentWithFragmentAndActivity_fragmentHasCorrectLifecycle() { + TranscriptFragment fragment = + FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class); + + assertThat(fragment.getLifecycleEvents()) + .containsExactly("onCreate", "onStart", "onResume") + .inOrder(); + assertThat(fragment.isVisible()).isTrue(); + } + + @Test + public void setupFragmentWithFragmentAndActivityAndBundle_HasCorrectLifecycle() { + Bundle testBundle = generateTestBundle(); + TranscriptFragment fragment = + FragmentController.setupFragment(new TranscriptFragment(), TestActivity.class, + testBundle); + + assertThat(fragment.getLifecycleEvents()) + .containsExactly("onCreate", "onStart", "onResume") + .inOrder(); + assertThat(fragment.isVisible()).isTrue(); + } + + @Test + public void + setupFragmentWithFragment_Activity_ContainViewIdAndBundle_HasCorrectLifecycle() { + Bundle testBundle = generateTestBundle(); + TranscriptFragment fragment = + FragmentController.setupFragment( + new TranscriptFragment(), + CustomizedViewIdTestActivity.class, + R.id.custom_activity_view, + testBundle); + + assertThat(fragment.getLifecycleEvents()) + .containsExactly("onCreate", "onStart", "onResume") + .inOrder(); + assertThat(fragment.isVisible()).isTrue(); + } + + private Intent generateTestIntent() { + final Intent testIntent = new Intent("test_action").putExtra("test_key", "test_value"); + return testIntent; + } + + private Bundle generateTestBundle() { + final Bundle testBundle = new Bundle(); + testBundle.putString("test_key", "test_value"); + + return testBundle; + } + + /** A Fragment which can record lifecycle status for test. */ + public static class TranscriptFragment extends Fragment { + + public static final List<String> sLifecycleEvents = new ArrayList<>(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + sLifecycleEvents.add("onCreate"); + } + + @Override + public void onStart() { + super.onStart(); + sLifecycleEvents.add("onStart"); + } + + @Override + public void onResume() { + super.onResume(); + sLifecycleEvents.add("onResume"); + } + + @Override + public void onPause() { + super.onPause(); + sLifecycleEvents.add("onPause"); + } + + @Override + public void onStop() { + super.onStop(); + sLifecycleEvents.add("onStop"); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_contents, container, false); + } + + public List<String> getLifecycleEvents() { + return sLifecycleEvents; + } + + public static void clearLifecycleEvents() { + sLifecycleEvents.clear(); + } + } + + /** A Activity which set a default view for test. */ + public static class TestActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + LinearLayout view = new LinearLayout(this); + view.setId(1); + + setContentView(view); + } + } + + /** A Activity which has a custom view for test. */ + public static class CustomizedViewIdTestActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.custom_activity_view); + } + } +} diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml new file mode 100644 index 000000000000..c074f30964db --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/custom_activity_view.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/custom_activity_view" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + +</LinearLayout> diff --git a/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml new file mode 100644 index 000000000000..425b2bb6a0d9 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/fragment/src/test/resources/res/layout/fragment_contents.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical"> + + <TextView + android:id="@+id/tacos" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="TACOS"/> + + <TextView + android:id="@+id/burritos" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="BURRITOS"/> + +</LinearLayout> diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 4a913c87bddf..bb72375499c1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -25,7 +25,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.ActivityManager; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -58,12 +57,10 @@ import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowSettings; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {UtilsTest.ShadowSecure.class, UtilsTest.ShadowLocationManager.class}) +@Config(shadows = {UtilsTest.ShadowLocationManager.class}) public class UtilsTest { private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100}; private static final String TAG = "UtilsTest"; @@ -94,7 +91,7 @@ public class UtilsTest { mContext = spy(RuntimeEnvironment.application); when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager); when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager); - ShadowSecure.reset(); + ShadowSettings.ShadowSecure.reset(); mAudioManager = mContext.getSystemService(AudioManager.class); } @@ -111,15 +108,16 @@ public class UtilsTest { Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS); assertThat(Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.LOCATION_CHANGER, Settings.Secure.LOCATION_CHANGER_UNKNOWN)) - .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS); + Settings.Secure.LOCATION_CHANGER, + Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo( + Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS); } @Test public void testFormatPercentage_RoundTrue_RoundUpIfPossible() { - final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, - PERCENTAGE_1, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, - PERCENTAGE_100}; + final String[] expectedPercentages = + {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49, + PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100}; for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) { final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true); @@ -129,9 +127,9 @@ public class UtilsTest { @Test public void testFormatPercentage_RoundFalse_NoRound() { - final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, - PERCENTAGE_0, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, - PERCENTAGE_100}; + final String[] expectedPercentages = + {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49, + PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100}; for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) { final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false); @@ -143,12 +141,7 @@ public class UtilsTest { public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() { Resources resources = mock(Resources.class); when(resources.getInteger( - eq( - com.android - .internal - .R - .integer - .config_storageManagerDaystoRetainDefault))) + eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault))) .thenReturn(60); assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60); } @@ -163,31 +156,6 @@ public class UtilsTest { return intent -> TextUtils.equals(expected, intent.getAction()); } - @Implements(value = Settings.Secure.class) - public static class ShadowSecure extends ShadowSettings.ShadowSecure { - private static Map<String, Integer> map = new HashMap<>(); - - @Implementation - public static boolean putIntForUser(ContentResolver cr, String name, int value, - int userHandle) { - map.put(name, value); - return true; - } - - @Implementation - public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { - if (map.containsKey(name)) { - return map.get(name); - } else { - return def; - } - } - - public static void reset() { - map.clear(); - } - } - @Implements(value = LocationManager.class) public static class ShadowLocationManager { @@ -337,9 +305,8 @@ public class UtilsTest { @Test public void getBatteryStatus_statusIsFull_returnFullString() { - final Intent intent = new Intent() - .putExtra(BatteryManager.EXTRA_LEVEL, 100) - .putExtra(BatteryManager.EXTRA_SCALE, 100); + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra( + BatteryManager.EXTRA_SCALE, 100); final Resources resources = mContext.getResources(); assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo( @@ -348,9 +315,8 @@ public class UtilsTest { @Test public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() { - final Intent intent = new Intent() - .putExtra(BatteryManager.EXTRA_LEVEL, 100) - .putExtra(BatteryManager.EXTRA_SCALE, 100); + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra( + BatteryManager.EXTRA_SCALE, 100); final Resources resources = mContext.getResources(); assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo( @@ -516,7 +482,6 @@ public class UtilsTest { when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus); when(mUsbPort.supportsComplianceWarnings()).thenReturn(true); when(mUsbPortStatus.isConnected()).thenReturn(true); - when(mUsbPortStatus.getComplianceWarnings()) - .thenReturn(new int[]{complianceWarningType}); + when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType}); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java index 44fdaec49f73..3de84464af2e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java @@ -23,13 +23,17 @@ import android.content.Context; import android.os.UserHandle; import android.provider.Settings; +import com.android.settingslib.testutils.shadow.ShadowSecure; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSecure.class}) public class AccessibilityUtilsTest { private Context mContext; @@ -46,7 +50,7 @@ public class AccessibilityUtilsTest { @Test public void getEnabledServicesFromSettings_badFormat_emptyResult() { - Settings.Secure.putStringForUser( + ShadowSecure.putStringForUser( mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ":", UserHandle.myUserId()); @@ -57,7 +61,7 @@ public class AccessibilityUtilsTest { @Test public void getEnabledServicesFromSettings_1Service_1result() { final ComponentName cn = new ComponentName("pkg", "serv"); - Settings.Secure.putStringForUser( + ShadowSecure.putStringForUser( mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, cn.flattenToString() + ":", UserHandle.myUserId()); @@ -70,7 +74,7 @@ public class AccessibilityUtilsTest { public void getEnabledServicesFromSettings_2Services_2results() { final ComponentName cn1 = new ComponentName("pkg", "serv"); final ComponentName cn2 = new ComponentName("pkg", "serv2"); - Settings.Secure.putStringForUser( + ShadowSecure.putStringForUser( mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, cn1.flattenToString() + ":" + cn2.flattenToString(), UserHandle.myUserId()); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java index cb62a735434d..f9505ddb7e2f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java @@ -37,6 +37,8 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.LongSparseArray; +import com.android.settingslib.testutils.shadow.ShadowPermissionChecker; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,7 +47,6 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowPermissionChecker; import java.time.Clock; import java.util.ArrayList; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 6444f3bd4341..4b61ff1177bd 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -1015,6 +1015,13 @@ public class CachedBluetoothDeviceTest { } @Test + public void setName_setDeviceNameIsEmpty() { + mCachedDevice.setName(""); + + verify(mDevice, never()).setAlias(any()); + } + + @Test public void getProfileConnectionState_nullProfile_returnDisconnected() { assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo( BluetoothProfile.STATE_DISCONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java index dd8d54a62ff4..a2e8c5956100 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java @@ -38,6 +38,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; @@ -167,6 +168,7 @@ public class MetricsFeatureProviderTest { } @Test + @LooperMode(LooperMode.Mode.PAUSED) public void getAttribution_notSet_shouldReturnUnknown() { final Activity activity = Robolectric.setupActivity(Activity.class); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java index d67d44b9035d..25833b3ddbba 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java @@ -36,6 +36,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.CujType; +import com.android.settingslib.testutils.OverpoweredReflectionHelper; import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; import org.junit.Before; @@ -51,7 +52,6 @@ import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; -import org.robolectric.util.ReflectionHelpers; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -83,8 +83,10 @@ public class SettingsJankMonitorTest { public void setUp() { ShadowInteractionJankMonitor.reset(); when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true); - ReflectionHelpers.setStaticField(SettingsJankMonitor.class, "scheduledExecutorService", - mScheduledExecutorService); + OverpoweredReflectionHelper + .setStaticField(SettingsJankMonitor.class, + "scheduledExecutorService", + mScheduledExecutorService); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java index cf702b531a3c..471dac05e6b4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java @@ -37,8 +37,10 @@ import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.LooperMode; @RunWith(RobolectricTestRunner.class) +@LooperMode(LooperMode.Mode.PAUSED) public class HideNonSystemOverlayMixinTest { private ActivityController<TestActivity> mActivityController; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java index 3475ff7d96f8..b009abd87b1c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java @@ -22,13 +22,14 @@ import android.content.Context; import android.os.UserManager; import android.provider.Settings; +import com.android.settingslib.testutils.shadow.ShadowUserManager; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowUserManager; @RunWith(RobolectricTestRunner.class) public class DevelopmentSettingsEnablerTest { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java index 8e33ca338eb1..0cabab241be4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.LooperMode; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; @@ -269,6 +270,7 @@ public class LicenseHtmlGeneratorFromXmlTest { } @Test + @LooperMode(LooperMode.Mode.PAUSED) public void testGenerateHtmlWithCustomHeading() throws Exception { List<File> xmlFiles = new ArrayList<>(); Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); @@ -292,6 +294,7 @@ public class LicenseHtmlGeneratorFromXmlTest { } @Test + @LooperMode(LooperMode.Mode.PAUSED) public void testGenerateNewHtmlWithCustomHeading() throws Exception { List<File> xmlFiles = new ArrayList<>(); Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index c058a61a3e9e..f22e090fe7df 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -19,6 +19,9 @@ import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP; import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; +import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP; + +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +35,7 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.NearbyDevice; +import android.media.RouteListingPreference; import android.os.Parcel; import com.android.settingslib.bluetooth.A2dpProfile; @@ -110,6 +114,8 @@ public class MediaDeviceTest { @Mock private MediaRouter2Manager mMediaRouter2Manager; + private RouteListingPreference.Item mItem; + private BluetoothMediaDevice mBluetoothMediaDevice1; private BluetoothMediaDevice mBluetoothMediaDevice2; private BluetoothMediaDevice mBluetoothMediaDevice3; @@ -497,4 +503,21 @@ public class MediaDeviceTest { assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0); } + + @Test + public void getSelectionBehavior_setItemWithSelectionBehaviorOnSystemRoute_returnTransfer() { + mItem = new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1) + .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP) + .build(); + mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1, + mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME, mItem); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo, + TEST_PACKAGE_NAME, mItem); + + assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java new file mode 100644 index 000000000000..9e9725fc3710 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@LooperMode(LooperMode.Mode.LEGACY) +package com.android.settingslib; + +import org.robolectric.annotation.LooperMode; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java index d41d5112e6b2..faec02f7a0d4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.LooperMode; @RunWith(RobolectricTestRunner.class) public class AnimatedImageViewTest { @@ -40,6 +41,7 @@ public class AnimatedImageViewTest { } @Test + @LooperMode(LooperMode.Mode.PAUSED) public void testAnimation_ViewVisible_AnimationRunning() { mAnimatedImageView.setVisibility(View.VISIBLE); mAnimatedImageView.setAnimating(true); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java index 0a48f19a3021..0d889139e8b4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java @@ -41,6 +41,8 @@ import androidx.annotation.ColorRes; import androidx.preference.PreferenceViewHolder; import androidx.preference.R; +import com.android.settingslib.testutils.OverpoweredReflectionHelper; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -502,14 +504,18 @@ public class BannerMessagePreferenceTest { private void assumeAndroidR() { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 30); ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "R"); - ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false); + OverpoweredReflectionHelper + .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false); // Reset view holder to use correct layout. } + + private void assumeAndroidS() { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 31); ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "S"); - ReflectionHelpers.setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true); + OverpoweredReflectionHelper + .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true); // Re-inflate view to update layout. setUpViewHolder(); } diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java new file mode 100644 index 000000000000..4fcc5a1f7000 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/OverpoweredReflectionHelper.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.testutils; + +import org.robolectric.util.ReflectionHelpers; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public class OverpoweredReflectionHelper extends ReflectionHelpers { + + /** + * Robolectric upstream does not rely on or encourage this behaviour. + * + * @param field + */ + private static void makeFieldVeryAccessible(Field field) { + field.setAccessible(true); + // remove 'final' modifier if present + if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { + Field modifiersField = getModifiersField(); + modifiersField.setAccessible(true); + try { + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } catch (IllegalAccessException e) { + + throw new AssertionError(e); + } + } + } + + private static Field getModifiersField() { + try { + return Field.class.getDeclaredField("modifiers"); + } catch (NoSuchFieldException e) { + try { + Method getFieldsMethod = + Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + getFieldsMethod.setAccessible(true); + Field[] fields = (Field[]) getFieldsMethod.invoke(Field.class, false); + for (Field modifiersField : fields) { + if ("modifiers".equals(modifiersField.getName())) { + return modifiersField; + } + } + } catch (ReflectiveOperationException innerE) { + throw new AssertionError(innerE); + } + } + throw new AssertionError(); + } + + /** + * Reflectively set the value of a static field. + * + * @param field Field object. + * @param fieldNewValue The new value. + */ + public static void setStaticField(Field field, Object fieldNewValue) { + try { + makeFieldVeryAccessible(field); + field.setAccessible(true); + field.set(null, fieldNewValue); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Reflectively set the value of a static field. + * + * @param clazz Target class. + * @param fieldName The field name. + * @param fieldNewValue The new value. + */ + public static void setStaticField(Class<?> clazz, String fieldName, Object fieldNewValue) { + try { + setStaticField(clazz.getDeclaredField(fieldName), fieldNewValue); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java index 924eb047c340..0b9ba8d044ce 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java @@ -16,23 +16,27 @@ package com.android.settingslib.testutils.shadow; +import static android.os.Build.VERSION_CODES.O; + import android.app.ActivityManager; +import android.app.IActivityManager; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import org.robolectric.shadow.api.Shadow; +import org.robolectric.util.ReflectionHelpers; @Implements(ActivityManager.class) public class ShadowActivityManager { private static int sCurrentUserId = 0; - private int mUserSwitchedTo = -1; + private static int sUserSwitchedTo = -1; @Resetter - public void reset() { + public static void reset() { sCurrentUserId = 0; - mUserSwitchedTo = 0; + sUserSwitchedTo = 0; } @Implementation @@ -42,16 +46,21 @@ public class ShadowActivityManager { @Implementation protected boolean switchUser(int userId) { - mUserSwitchedTo = userId; + sUserSwitchedTo = userId; return true; } + @Implementation(minSdk = O) + protected static IActivityManager getService() { + return ReflectionHelpers.createNullProxy(IActivityManager.class); + } + public boolean getSwitchUserCalled() { - return mUserSwitchedTo != -1; + return sUserSwitchedTo != -1; } public int getUserSwitchedTo() { - return mUserSwitchedTo; + return sUserSwitchedTo; } public static void setCurrentUser(int userId) { diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java index 2c0792fd57bd..bbfdb7f91c4b 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java @@ -29,7 +29,7 @@ public class ShadowDefaultDialerManager { private static String sDefaultDialer; @Resetter - public void reset() { + public static void reset() { sDefaultDialer = null; } diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java new file mode 100644 index 000000000000..fae3aeafd5fb --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.testutils.shadow; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.AttributionSource; +import android.content.Context; +import android.content.PermissionChecker; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.util.HashMap; +import java.util.Map; +/** Shadow class of {@link PermissionChecker}. */ +@Implements(PermissionChecker.class) +public class ShadowPermissionChecker { + private static final Map<String, Map<String, Integer>> RESULTS = new HashMap<>(); + /** Set the result of permission check for a specific permission. */ + public static void setResult(String packageName, String permission, int result) { + if (!RESULTS.containsKey(packageName)) { + RESULTS.put(packageName, new HashMap<>()); + } + RESULTS.get(packageName).put(permission, result); + } + /** Check the permission of calling package. */ + @Implementation + public static int checkCallingPermissionForDataDelivery( + Context context, + String permission, + String packageName, + String attributionTag, + String message) { + return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission) + ? RESULTS.get(packageName).get(permission) + : PermissionChecker.checkCallingPermissionForDataDelivery( + context, permission, packageName, attributionTag, message); + } + /** Check general permission. */ + @Implementation + public static int checkPermissionForDataDelivery( + Context context, + String permission, + int pid, + int uid, + String packageName, + String attributionTag, + String message) { + return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission) + ? RESULTS.get(packageName).get(permission) + : PermissionChecker.checkPermissionForDataDelivery( + context, permission, pid, uid, packageName, attributionTag, message); + } + /** Check general permission. */ + @Implementation + public static int checkPermissionForPreflight(@NonNull Context context, + @NonNull String permission, int pid, int uid, @Nullable String packageName) { + return checkPermissionForPreflight(context, permission, new AttributionSource( + uid, packageName, null /*attributionTag*/)); + } + /** Check general permission. */ + @Implementation + public static int checkPermissionForPreflight(@NonNull Context context, + @NonNull String permission, @NonNull AttributionSource attributionSource) { + final String packageName = attributionSource.getPackageName(); + return RESULTS.containsKey(packageName) && RESULTS.get(packageName).containsKey(permission) + ? RESULTS.get(packageName).get(permission) + : PermissionChecker.checkPermissionForPreflight( + context, permission, attributionSource); + } +} diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java new file mode 100644 index 000000000000..70ebc6791538 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSecure.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.testutils.shadow; + +import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; + +import android.content.ContentResolver; +import android.provider.Settings; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowSettings; + +@Implements(value = Settings.Secure.class) +public class ShadowSecure extends ShadowSettings.ShadowSecure { + @Implementation(minSdk = JELLY_BEAN_MR1) + public static boolean putStringForUser(ContentResolver cr, String name, String value, + int userHandle) { + return putString(cr, name, value); + } +} diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java index 381d072b6fe1..5ac0a87c1ae9 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowSmsApplication.java @@ -31,7 +31,7 @@ public class ShadowSmsApplication { private static ComponentName sDefaultSmsApplication; @Resetter - public void reset() { + public static void reset() { sDefaultSmsApplication = null; } diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java index ca1eefcad7de..60d7721242b8 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowUserManager.java @@ -16,20 +16,28 @@ package com.android.settingslib.testutils.shadow; +import static android.os.Build.VERSION_CODES.N_MR1; + import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.UserInfo; +import android.content.pm.UserProperties; +import android.os.UserHandle; import android.os.UserManager; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowBuild; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Implements(value = UserManager.class) public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { private List<UserInfo> mUserInfos = addProfile(0, "Owner"); + private final Map<Integer, UserProperties> mUserPropertiesMap = new HashMap<>(); @Implementation protected static UserManager get(Context context) { @@ -62,4 +70,37 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager protected List<UserInfo> getProfiles(@UserIdInt int userHandle) { return getProfiles(); } + + /** + * @return {@code false} by default, or the value specified via {@link #setIsAdminUser(boolean)} + */ + @Implementation(minSdk = N_MR1) + public boolean isAdminUser() { + return getUserInfo(UserHandle.myUserId()).isAdmin(); + } + + /** + * Sets that the current user is an admin user; controls the return value of + * {@link UserManager#isAdminUser}. + */ + public void setIsAdminUser(boolean isAdminUser) { + UserInfo userInfo = getUserInfo(UserHandle.myUserId()); + if (isAdminUser) { + userInfo.flags |= UserInfo.FLAG_ADMIN; + } else { + userInfo.flags &= ~UserInfo.FLAG_ADMIN; + } + } + + public void setupUserProperty(int userId, int showInSettings) { + UserProperties userProperties = new UserProperties(new UserProperties.Builder() + .setShowInSettings(showInSettings).build()); + mUserPropertiesMap.putIfAbsent(userId, userProperties); + } + + @Implementation(minSdk = ShadowBuild.UPSIDE_DOWN_CAKE) + protected UserProperties getUserProperties(UserHandle user) { + return mUserPropertiesMap.getOrDefault(user.getIdentifier(), + new UserProperties(new UserProperties.Builder().build())); + } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index dd8eb3b3f3fb..c740423c39af 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -345,6 +345,8 @@ public class SettingsBackupTest { Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, Settings.Global.MOBILE_DATA, // Candidate for backup? Settings.Global.MOBILE_DATA_ALWAYS_ON, + Settings.Global.DSRM_DURATION_MILLIS, + Settings.Global.DSRM_ENABLED_ACTIONS, Settings.Global.MODE_RINGER, Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, Settings.Global.MULTI_SIM_SMS_PROMPT, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index a443b5c4aa60..73fb0f03052b 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -65,6 +65,7 @@ systemui_compose_java_defaults { "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", "androidx.activity_activity-compose", + "androidx.compose.animation_animation-graphics", ], // By default, Compose is disabled and we compile the ComposeFacade diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2913c169a0be..4fd47232a0df 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -865,7 +865,7 @@ <activity android:name=".settings.brightness.BrightnessDialog" android:label="@string/quick_settings_brightness_dialog_title" - android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog" + android:theme="@style/BrightnessDialog" android:finishOnCloseSystemDialogs="true" android:launchMode="singleInstance" android:excludeFromRecents="true" diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt index fbd7f83ad350..1674591c30b5 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt @@ -17,9 +17,9 @@ package com.android.systemui.compose import android.view.View +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewTreeLifecycleOwner -import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistryController import androidx.savedstate.SavedStateRegistryOwner import com.android.compose.animation.ViewTreeSavedStateRegistryOwner @@ -27,7 +27,7 @@ import com.android.systemui.lifecycle.ViewLifecycleOwner internal object ComposeInitializerImpl : ComposeInitializer { override fun onAttachedToWindow(root: View) { - if (ViewTreeLifecycleOwner.get(root) != null) { + if (root.findViewTreeLifecycleOwner() != null) { error("root $root already has a LifecycleOwner") } @@ -54,7 +54,8 @@ internal object ComposeInitializerImpl : ComposeInitializer { override val savedStateRegistry = savedStateRegistryController.savedStateRegistry - override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle + override val lifecycle: Lifecycle + get() = lifecycleOwner.lifecycle } // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner] @@ -64,13 +65,13 @@ internal object ComposeInitializerImpl : ComposeInitializer { // Set the owners on the root. They will be reused by any ComposeView inside the root // hierarchy. - ViewTreeLifecycleOwner.set(root, lifecycleOwner) + root.setViewTreeLifecycleOwner(lifecycleOwner) ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner) } override fun onDetachedFromWindow(root: View) { - (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy() - ViewTreeLifecycleOwner.set(root, null) + (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy() + root.setViewTreeLifecycleOwner(null) ViewTreeSavedStateRegistryOwner.set(root, null) } } diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index 8f8bb1bc2d5a..c6438c9ef955 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -34,6 +34,7 @@ android_library { "PlatformComposeCore", "androidx.compose.runtime_runtime", + "androidx.compose.animation_animation-graphics", "androidx.compose.material3_material3", "androidx.activity_activity-compose", ], diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 323fed0c11f3..85178bc26a62 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalAnimationApi::class) +@file:OptIn(ExperimentalAnimationApi::class, ExperimentalAnimationGraphicsApi::class) package com.android.systemui.bouncer.ui.composable @@ -29,11 +29,14 @@ import androidx.compose.animation.core.Transition import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.keyframes import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition -import androidx.compose.foundation.Canvas +import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.Image import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -61,8 +64,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Constraints @@ -70,6 +75,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid +import com.android.internal.R.id.image import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance import com.android.systemui.bouncer.ui.viewmodel.EnteredKey @@ -139,7 +145,8 @@ private fun PinInputDisplay(viewModel: PinBouncerViewModel) { else -> EntryVisibility.Hidden } - ObscuredInputEntry(updateTransition(visibility, label = "Pin Entry $entry")) + val shape = viewModel.pinShapes.getShape(entry.sequenceNumber) + PinInputEntry(shape, updateTransition(visibility, label = "Pin Entry $entry")) LaunchedEffect(entry) { // Remove entry from visiblePinEntries once the hide transition completed. @@ -171,15 +178,11 @@ private sealed class EntryVisibility { } @Composable -private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) { +private fun PinInputEntry(shapeResourceId: Int, transition: Transition<EntryVisibility>) { // spec: http://shortn/_DEhE3Xl2bi - val shapePadding = 6.dp - val shapeOvershootSize = 22.dp val dismissStaggerDelayMs = 33 val dismissDurationMs = 450 val expansionDurationMs = 250 - val shapeExpandDurationMs = 83 - val shapeRetractDurationMs = 167 val shapeCollapseDurationMs = 200 val animatedEntryWidth by @@ -194,19 +197,17 @@ private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) { }, label = "entry space" ) { state -> - if (state == EntryVisibility.Shown) entryShapeSize + (shapePadding * 2) else 0.dp + if (state == EntryVisibility.Shown) entryShapeSize else 0.dp } val animatedShapeSize by transition.animateDp( transitionSpec = { when { - EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> - keyframes { - durationMillis = shapeExpandDurationMs + shapeRetractDurationMs - 0.dp at 0 with Easings.Linear - shapeOvershootSize at shapeExpandDurationMs with Easings.Legacy - } + EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> { + // The AVD contains the entry transition. + snap() + } targetState is EntryVisibility.BulkHidden -> { val target = targetState as EntryVisibility.BulkHidden tween( @@ -220,17 +221,21 @@ private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) { }, label = "shape size" ) { state -> - when (state) { - EntryVisibility.Shown -> entryShapeSize - else -> 0.dp - } + if (state == EntryVisibility.Shown) entryShapeSize else 0.dp } val dotColor = MaterialTheme.colorScheme.onSurfaceVariant Layout( content = { - // TODO(b/282730134): add support for dot shapes. - Canvas(Modifier) { drawCircle(dotColor) } + val image = AnimatedImageVector.animatedVectorResource(shapeResourceId) + var atEnd by remember { mutableStateOf(false) } + Image( + painter = rememberAnimatedVectorPainter(image, atEnd), + contentDescription = null, + contentScale = ContentScale.Crop, + colorFilter = ColorFilter.tint(dotColor), + ) + LaunchedEffect(Unit) { atEnd = true } } ) { measurables, _ -> val shapeSizePx = animatedShapeSize.roundToPx() @@ -507,7 +512,7 @@ private suspend fun showFailureAnimation( } } -private val entryShapeSize = 16.dp +private val entryShapeSize = 30.dp private val pinButtonSize = 84.dp private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 1811c02d549d..64c0f99f4ba7 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -128,6 +128,16 @@ public interface BcSmartspaceDataPlugin extends Plugin { void setDozeAmount(float amount); /** + * Set if dozing is true or false + */ + default void setDozing(boolean dozing) {} + + /** + * Set if split shade enabled + */ + default void setSplitShadeEnabled(boolean enabled) {} + + /** * Set the current keyguard bypass enabled status. */ default void setKeyguardBypassEnabled(boolean enabled) {} diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml new file mode 100644 index 000000000000..360ef2672e75 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.systemui.biometrics.UdfpsKeyguardView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. --> + +</com.android.systemui.biometrics.UdfpsKeyguardView> diff --git a/packages/SystemUI/res/drawable/pin_dot_avd.xml b/packages/SystemUI/res/drawable/pin_dot_avd.xml index 1c162511411a..710ba83a7e94 100644 --- a/packages/SystemUI/res/drawable/pin_dot_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_avd.xml @@ -1 +1,40 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.237000000000002" android:translateY="23.112000000000002"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector + android:height="30dp" + android:width="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="28.237000000000002" + android:translateY="23.112000000000002"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="translateX" + android:duration="500" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml index 0f8703f731fc..72a03bf54d0b 100644 --- a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml @@ -1 +1,130 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.54" android:translateY="23.54"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="1" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="150" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="200" android:startOffset="150" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="150" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="50" android:startOffset="150" android:valueFrom="0" android:valueTo="2" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="150" + android:propertyName="scaleX" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0.43" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="150" + android:propertyName="scaleY" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0.43" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="fillAlpha" + android:duration="200" + android:startOffset="150" + android:valueFrom="1" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="200" + android:propertyName="scaleX" + android:startOffset="150" + android:valueFrom="0.65" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="200" + android:propertyName="scaleY" + android:startOffset="150" + android:valueFrom="0.65" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="500" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="30dp" + android:height="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:pivotX="-13.54" + android:pivotY="-8.54" + android:scaleX="1" + android:scaleY="1" + android:translateX="28.54" + android:translateY="23.54"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:pivotX="317.509" + android:pivotY="-826.986" + android:scaleX="0.65" + android:scaleY="0.65" + android:translateX="-302.509" + android:translateY="841.986"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M317.51 -821.99 C314.75,-821.99 312.51,-824.23 312.51,-826.99 C312.51,-829.74 314.75,-831.99 317.51,-831.99 C320.27,-831.99 322.51,-829.74 322.51,-826.99 C322.51,-824.23 320.27,-821.99 317.51,-821.99z M317.51 -829.99 C315.86,-829.99 314.51,-828.64 314.51,-826.99 C314.51,-825.33 315.86,-823.99 317.51,-823.99 C319.16,-823.99 320.51,-825.33 320.51,-826.99 C320.51,-828.64 319.16,-829.99 317.51,-829.99z " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml index f1fb2aaf0a02..02abb99bdb27 100644 --- a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml @@ -1 +1,141 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " + android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="283" + android:propertyName="pathData" + android:startOffset="67" + android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " + android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="0" + android:propertyName="scaleY" + android:startOffset="67" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="67" + android:propertyName="scaleX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="scaleY" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="283" + android:propertyName="scaleX" + android:startOffset="67" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="283" + android:propertyName="scaleY" + android:startOffset="67" + android:valueFrom="1" + android:valueTo="0.3" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="500" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="30dp" + android:height="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:scaleY="0" + android:translateX="15.441" + android:translateY="15.691"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="15" + android:translateY="15"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml index 3717db8383ef..160732713102 100644 --- a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml @@ -1 +1,148 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="13.205" android:pivotY="1.795" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector + android:height="30dp" + android:width="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="15.397" + android:translateY="15.691" + android:scaleY="0"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="15" + android:translateY="13.205" + android:pivotY="1.795"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="pathData" + android:duration="67" + android:startOffset="0" + android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " + android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="pathData" + android:duration="283" + android:startOffset="67" + android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " + android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleY" + android:duration="0" + android:startOffset="67" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="pathData" + android:duration="67" + android:startOffset="0" + android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " + android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="pathData" + android:duration="283" + android:startOffset="67" + android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " + android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleX" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleY" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="translateX" + android:duration="500" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml index 95b804431b4a..78e2249b7cc6 100644 --- a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml @@ -1 +1,141 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector + android:height="30dp" + android:width="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="15.441" + android:translateY="15.691" + android:scaleY="0"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="15" + android:translateY="15"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="pathData" + android:duration="67" + android:startOffset="0" + android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " + android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="pathData" + android:duration="283" + android:startOffset="67" + android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " + android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleY" + android:duration="0" + android:startOffset="67" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleX" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleY" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleX" + android:duration="283" + android:startOffset="67" + android:valueFrom="1" + android:valueTo="0.4" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleY" + android:duration="283" + android:startOffset="67" + android:valueFrom="1" + android:valueTo="0.4" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="translateX" + android:duration="500" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml index 8ea8f857c71f..35c7210e1e3e 100644 --- a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml @@ -1 +1,119 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="0" + android:propertyName="scaleY" + android:startOffset="67" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " + android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="283" + android:propertyName="pathData" + android:startOffset="67" + android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " + android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="67" + android:propertyName="scaleX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="scaleY" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="500" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="30dp" + android:height="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:scaleY="0" + android:translateX="15.441" + android:translateY="15.691"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="15" + android:translateY="15"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml index 3779c805136e..9458b2ef479d 100644 --- a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml @@ -1 +1,148 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="17.64" android:pivotY="-2.64" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector + android:height="30dp" + android:width="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="15.397" + android:translateY="15.691" + android:scaleY="0"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="15" + android:translateY="17.64" + android:pivotY="-2.64"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="pathData" + android:duration="67" + android:startOffset="0" + android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " + android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="pathData" + android:duration="283" + android:startOffset="67" + android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " + android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleY" + android:duration="0" + android:startOffset="67" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="pathData" + android:duration="67" + android:startOffset="0" + android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " + android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="pathData" + android:duration="283" + android:startOffset="67" + android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " + android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleX" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleY" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="translateX" + android:duration="500" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml index ddda94a4edcc..06e27df27f69 100644 --- a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml +++ b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml @@ -1 +1,142 @@ -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15.859" android:pivotY="-0.859" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector + android:height="30dp" + android:width="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="15.397" + android:translateY="15.691" + android:scaleY="0"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="15" + android:translateY="15.859" + android:pivotY="-0.859"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillColor="#ffffff" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="pathData" + android:duration="67" + android:startOffset="0" + android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " + android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="pathData" + android:duration="283" + android:startOffset="67" + android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " + android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleY" + android:duration="0" + android:startOffset="67" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="scaleX" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleY" + android:duration="67" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleX" + android:duration="283" + android:startOffset="67" + android:valueFrom="1" + android:valueTo="0.35000000000000003" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:propertyName="scaleY" + android:duration="283" + android:startOffset="67" + android:valueFrom="1" + android:valueTo="0.35000000000000003" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:propertyName="translateX" + android:duration="500" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml index 7105721aff70..21e0d2c0b8d7 100644 --- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml +++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml @@ -164,7 +164,6 @@ /> <ImageView android:id="@+id/media_output_item_end_click_icon" - android:src="@drawable/media_output_status_edit_session" android:layout_width="24dp" android:layout_height="24dp" android:focusable="false" diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml index 6cc72ddca42a..d9f4b7961636 100644 --- a/packages/SystemUI/res/layout/screen_record_options.xml +++ b/packages/SystemUI/res/layout/screen_record_options.xml @@ -55,14 +55,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" + android:gravity="center_vertical" android:layout_marginTop="@dimen/screenrecord_option_padding"> <ImageView android:layout_width="@dimen/screenrecord_option_icon_size" android:layout_height="@dimen/screenrecord_option_icon_size" - android:layout_weight="0" android:src="@drawable/ic_touch" android:tint="?android:attr/textColorSecondary" - android:layout_gravity="center_vertical" android:layout_marginRight="@dimen/screenrecord_option_padding" android:importantForAccessibility="no"/> <TextView @@ -70,7 +69,6 @@ android:layout_height="wrap_content" android:minHeight="48dp" android:layout_weight="1" - android:gravity="center_vertical" android:text="@string/screenrecord_taps_label" android:textAppearance="?android:attr/textAppearanceMedium" android:fontFamily="@*android:string/config_bodyFontFamily" @@ -80,7 +78,6 @@ android:layout_width="wrap_content" android:minWidth="48dp" android:layout_height="48dp" - android:layout_weight="0" android:id="@+id/screenrecord_taps_switch" style="@style/ScreenRecord.Switch" android:importantForAccessibility="yes"/> diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml index 00af7f4e10e3..530d752732c1 100644 --- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml +++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml @@ -16,8 +16,7 @@ --> <com.android.systemui.biometrics.UdfpsKeyguardViewLegacy xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/udfps_animation_view" + android:id="@+id/udfps_animation_view_legacy" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 59becc69506c..9a2db6bfdef6 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -20,6 +20,13 @@ On large screens should be the same as the regular status bar. --> <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen> + <!-- padding for container with status icons and battery --> + <dimen name="status_bar_icons_padding_end">12dp</dimen> + <!-- start padding is smaller to account for status icon margins coming from drawable itself --> + <dimen name="status_bar_icons_padding_start">11dp</dimen> + + <dimen name="status_bar_padding_end">0dp</dimen> + <!-- Size of user icon + frame in the qs user picker (incl. frame) --> <dimen name="qs_framed_avatar_size">60dp</dimen> <!-- Size of user icon + frame in the keyguard user picker (incl. frame) --> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index d277daefd928..d053a7a0d0bb 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -16,13 +16,9 @@ */ --> <resources> - <!-- padding for container with status icons and battery --> - <dimen name="status_bar_icons_padding_end">12dp</dimen> - <!-- it's a bit smaller on large screen to account for status_bar_icon_horizontal_margin --> + <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin --> <dimen name="status_bar_icons_padding_start">10dp</dimen> - <dimen name="status_bar_padding_end">0dp</dimen> - <!-- gap on either side of status bar notification icons --> <dimen name="status_bar_icon_horizontal_margin">1dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index fd74c7eae361..6b8562105c74 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -398,7 +398,8 @@ <item name="android:itemTextAppearance">@style/Control.MenuItem</item> </style> - <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> + <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match --> + <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt index 64234c205617..e02e592c32cc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt @@ -18,22 +18,23 @@ package com.android.systemui.util import android.os.Trace import android.os.TraceNameSupplier +import java.util.concurrent.atomic.AtomicInteger /** - * Run a block within a [Trace] section. - * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block. + * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection] + * after the passed block. */ inline fun <T> traceSection(tag: String, block: () -> T): T = - if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) { - Trace.traceBegin(Trace.TRACE_TAG_APP, tag) - try { - block() - } finally { - Trace.traceEnd(Trace.TRACE_TAG_APP) - } - } else { + if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) { + Trace.traceBegin(Trace.TRACE_TAG_APP, tag) + try { block() + } finally { + Trace.traceEnd(Trace.TRACE_TAG_APP) } + } else { + block() + } class TraceUtils { companion object { @@ -43,6 +44,7 @@ class TraceUtils { /** * Helper function for creating a Runnable object that implements TraceNameSupplier. + * * This is useful for posting Runnables to Handlers with meaningful names. */ inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable { @@ -51,5 +53,37 @@ class TraceUtils { override fun run() = block() } } + + /** + * Cookie used for async traces. Shouldn't be public, but to use it inside inline methods + * there is no other way around. + */ + val lastCookie = AtomicInteger(0) + + /** + * Creates an async slice in a track called "AsyncTraces". + * + * This can be used to trace coroutine code. Note that all usages of this method will appear + * under a single track. + */ + inline fun <T> traceAsync(method: String, block: () -> T): T = + traceAsync(method, "AsyncTraces", block) + + /** + * Creates an async slice in a track with [trackName] while [block] runs. + * + * This can be used to trace coroutine code. [method] will be the name of the slice, + * [trackName] of the track. The track is one of the rows visible in a perfetto trace inside + * SystemUI process. + */ + inline fun <T> traceAsync(method: String, trackName: String, block: () -> T): T { + val cookie = lastCookie.incrementAndGet() + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie) + try { + return block() + } finally { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie) + } + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index dba124685fe1..6c983762f9a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -352,6 +352,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean splitShadeEnabled) { + mSmartspaceController.setSplitShadeEnabled(splitShadeEnabled); + } + + /** * Set which clock should be displayed on the keyguard. The other one will be automatically * hidden. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 6f596843bf9f..3e16d559742d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -43,15 +43,6 @@ public class KeyguardPinViewController private long mPinLength; private boolean mDisabledAutoConfirmation; - /** - * Responsible for identifying if PIN hinting is to be enabled or not - */ - private boolean mIsPinHinting; - - /** - * Responsible for identifying if auto confirm is enabled or not in Settings - */ - private boolean mIsAutoPinConfirmEnabledInSettings; protected KeyguardPinViewController(KeyguardPINView view, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -72,9 +63,6 @@ public class KeyguardPinViewController mFeatureFlags = featureFlags; mBackspaceKey = view.findViewById(R.id.delete_button); mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()); - mIsPinHinting = mPinLength == DEFAULT_PIN_LENGTH; - mIsAutoPinConfirmEnabledInSettings = mLockPatternUtils.isAutoPinConfirmEnabled( - KeyguardUpdateMonitor.getCurrentUser()); } @Override @@ -94,7 +82,7 @@ public class KeyguardPinViewController protected void onUserInput() { super.onUserInput(); - if (mIsAutoPinConfirmEnabledInSettings) { + if (isAutoPinConfirmEnabledInSettings()) { updateAutoConfirmationState(); if (mPasswordEntry.getText().length() == mPinLength && mOkButton.getVisibility() == View.INVISIBLE) { @@ -142,7 +130,7 @@ public class KeyguardPinViewController * Updates the visibility of the OK button for auto confirm feature */ private void updateOKButtonVisibility() { - if (mIsPinHinting && !mDisabledAutoConfirmation) { + if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) { mOkButton.setVisibility(View.INVISIBLE); } else { mOkButton.setVisibility(View.VISIBLE); @@ -154,9 +142,10 @@ public class KeyguardPinViewController * Visibility changes are only for auto confirmation configuration. */ private void updateBackSpaceVisibility() { + boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings(); mBackspaceKey.setTransparentMode(/* isTransparentMode= */ - mIsAutoPinConfirmEnabledInSettings && !mDisabledAutoConfirmation); - if (mIsAutoPinConfirmEnabledInSettings) { + isAutoConfirmation && !mDisabledAutoConfirmation); + if (isAutoConfirmation) { if (mPasswordEntry.getText().length() > 0 || mDisabledAutoConfirmation) { mBackspaceKey.setVisibility(View.VISIBLE); @@ -166,8 +155,24 @@ public class KeyguardPinViewController } } /** Updates whether to use pin hinting or not. */ - private void updatePinHinting() { - mPasswordEntry.setIsPinHinting(mIsAutoPinConfirmEnabledInSettings && mIsPinHinting + void updatePinHinting() { + mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting() && !mDisabledAutoConfirmation); } + + /** + * Responsible for identifying if PIN hinting is to be enabled or not + */ + private boolean isPinHinting() { + return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()) + == DEFAULT_PIN_LENGTH; + } + + /** + * Responsible for identifying if auto confirm is enabled or not in Settings + */ + private boolean isAutoPinConfirmEnabledInSettings() { + //Checks if user has enabled the auto confirm in Settings + return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser()); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 880f242c5938..458ca2b17c6d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -544,7 +544,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) { if (mCancelAction != null) { mCancelAction.run(); - mCancelAction = null; } mDismissAction = action; mCancelAction = cancelAction; @@ -782,9 +781,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard case SimPuk: // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser())) { - finish = true; + boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled( + KeyguardUpdateMonitor.getCurrentUser()); + if (securityMode == SecurityMode.None || isLockscreenDisabled) { + finish = isLockscreenDisabled; eventSubtype = BOUNCER_DISMISS_SIM; uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 00500d617766..6854c97c3415 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -323,6 +323,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean enabled) { + mKeyguardClockSwitchController.setSplitShadeEnabled(enabled); + } + + /** * Updates the alignment of the KeyguardStatusView and animates the transition if requested. */ public void updateAlignment( @@ -350,6 +357,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + /* This transition blocks any layout changes while running. For that reason + * special logic with setting visibility was added to {@link BcSmartspaceView#setDozing} + * for split shade to avoid jump of the media object. */ ChangeBounds transition = new ChangeBounds(); if (splitShadeEnabled) { // Excluding media from the transition on split-shade, as it doesn't transition diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index d1fffaa926ea..76b073ef73af 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -32,7 +32,6 @@ import android.widget.ImageView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; @@ -126,7 +125,6 @@ public class LockIconView extends FrameLayout implements Dumpable { /** * Set the location of the lock icon. */ - @VisibleForTesting public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) { mLockIconCenter = center; mRadius = radius; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 239a0cc01c45..8ef6c2e0d377 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -56,10 +56,12 @@ import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.biometrics.UdfpsController; +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -112,6 +114,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final VibratorHelper mVibrator; @Nullable private final AuthRippleController mAuthRippleController; @NonNull private final FeatureFlags mFeatureFlags; + @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; @NonNull private final KeyguardTransitionInteractor mTransitionInteractor; @NonNull private final KeyguardInteractor mKeyguardInteractor; @@ -180,7 +183,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull @Main Resources resources, @NonNull KeyguardTransitionInteractor transitionInteractor, @NonNull KeyguardInteractor keyguardInteractor, - @NonNull FeatureFlags featureFlags + @NonNull FeatureFlags featureFlags, + PrimaryBouncerInteractor primaryBouncerInteractor ) { super(view); mStatusBarStateController = statusBarStateController; @@ -197,6 +201,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mTransitionInteractor = transitionInteractor; mKeyguardInteractor = keyguardInteractor; mFeatureFlags = featureFlags; + mPrimaryBouncerInteractor = primaryBouncerInteractor; mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); @@ -325,8 +330,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setContentDescription(null); } + boolean accessibilityEnabled = + !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser(); + mView.setImportantForAccessibility( + accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES + : View.IMPORTANT_FOR_ACCESSIBILITY_NO); + if (!Objects.equals(prevContentDescription, mView.getContentDescription()) - && mView.getContentDescription() != null && mView.isVisibleToUser()) { + && mView.getContentDescription() != null && accessibilityEnabled) { mView.announceForAccessibility(mView.getContentDescription()); } } @@ -387,15 +398,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private void updateLockIconLocation() { final float scaleFactor = mAuthController.getScaleFactor(); final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); - if (mUdfpsSupported) { - mView.setCenterLocation(mAuthController.getUdfpsLocation(), - mAuthController.getUdfpsRadius(), scaledPadding); - } else { - mView.setCenterLocation( - new Point((int) mWidthPixels / 2, - (int) (mHeightPixels - - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + if (mUdfpsSupported) { + mView.setCenterLocation(mAuthController.getUdfpsLocation(), + mAuthController.getUdfpsRadius(), scaledPadding); + } else { + mView.setCenterLocation( + new Point((int) mWidthPixels / 2, + (int) (mHeightPixels + - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), sLockIconRadiusPx * scaleFactor, scaledPadding); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt index 670c1fa45e5c..c46558532372 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -56,9 +56,14 @@ class ScreenDecorHwcLayer( ) : DisplayCutoutBaseView(context) { val colorMode: Int private val useInvertedAlphaColor: Boolean - private val color: Int + private var color: Int = Color.BLACK + set(value) { + field = value + paint.color = value + } + private val bgColor: Int - private val cornerFilter: ColorFilter + private var cornerFilter: ColorFilter private val cornerBgFilter: ColorFilter private val clearPaint: Paint @JvmField val transparentRect: Rect = Rect() @@ -109,10 +114,16 @@ class ScreenDecorHwcLayer( override fun onAttachedToWindow() { super.onAttachedToWindow() parent.requestTransparentRegion(this) + updateColors() + } + + private fun updateColors() { if (!debug) { viewRootImpl.setDisplayDecoration(true) } + cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + if (useInvertedAlphaColor) { paint.set(clearPaint) } else { @@ -121,6 +132,21 @@ class ScreenDecorHwcLayer( } } + fun setDebugColor(color: Int) { + if (!debug) { + return + } + + if (this.color == color) { + return + } + + this.color = color + + updateColors() + invalidate() + } + override fun onUpdate() { parent.requestTransparentRegion(this) } @@ -367,7 +393,7 @@ class ScreenDecorHwcLayer( /** * Update the rounded corner drawables. */ - fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) { + fun updateRoundedCornerDrawable(top: Drawable?, bottom: Drawable?) { roundedCornerDrawableTop = top roundedCornerDrawableBottom = bottom updateRoundedCornerDrawableBounds() diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 67d4a2e25051..ff395da54d18 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -36,6 +36,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.hardware.graphics.common.AlphaInterpretation; @@ -69,9 +70,9 @@ import com.android.internal.util.Preconditions; import com.android.settingslib.Utils; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.DebugRoundedCornerDelegate; +import com.android.systemui.decor.DebugRoundedCornerModel; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; @@ -80,10 +81,12 @@ import com.android.systemui.decor.OverlayWindow; import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.decor.RoundedCornerDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegateImpl; +import com.android.systemui.decor.ScreenDecorCommand; import com.android.systemui.log.ScreenDecorationsLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; @@ -95,7 +98,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.Executor; import javax.inject.Inject; @@ -130,7 +132,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @VisibleForTesting protected boolean mIsRegistered; private final Context mContext; - private final Executor mMainExecutor; + private final CommandRegistry mCommandRegistry; private final SecureSettings mSecureSettings; @VisibleForTesting DisplayTracker.Callback mDisplayListener; @@ -170,7 +172,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { private int mTintColor = Color.BLACK; @VisibleForTesting protected DisplayDecorationSupport mHwcScreenDecorationSupport; - private Display.Mode mDisplayMode; + private final Point mDisplaySize = new Point(); @VisibleForTesting protected DisplayInfo mDisplayInfo = new DisplayInfo(); private DisplayCutout mDisplayCutout; @@ -313,8 +315,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @Inject public ScreenDecorations(Context context, - @Main Executor mainExecutor, SecureSettings secureSettings, + CommandRegistry commandRegistry, UserTracker userTracker, DisplayTracker displayTracker, PrivacyDotViewController dotViewController, @@ -324,8 +326,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { ScreenDecorationsLogger logger, AuthController authController) { mContext = context; - mMainExecutor = mainExecutor; mSecureSettings = secureSettings; + mCommandRegistry = commandRegistry; mUserTracker = userTracker; mDisplayTracker = displayTracker; mDotViewController = dotViewController; @@ -350,6 +352,45 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } }; + private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> { + // If we are exiting debug mode, we can set it (false) and bail, otherwise we will + // ensure that debug mode is set + if (cmd.getDebug() != null && !cmd.getDebug()) { + setDebug(false); + return; + } else { + // setDebug is idempotent + setDebug(true); + } + + if (cmd.getColor() != null) { + mDebugColor = cmd.getColor(); + mExecutor.execute(() -> { + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.setDebugColor(cmd.getColor()); + } + updateColorInversionDefault(); + }); + } + + DebugRoundedCornerModel roundedTop = null; + DebugRoundedCornerModel roundedBottom = null; + if (cmd.getRoundedTop() != null) { + roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel(); + } + if (cmd.getRoundedBottom() != null) { + roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel(); + } + if (roundedTop != null || roundedBottom != null) { + mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom); + mExecutor.execute(() -> { + removeAllOverlays(); + removeHwcOverlay(); + setupDecorations(); + }); + } + }; + @Override public void start() { if (DEBUG_DISABLE_SCREEN_DECORATIONS) { @@ -361,6 +402,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mExecutor.execute(this::startOnScreenDecorationsThread); mDotViewController.setUiExecutor(mExecutor); mAuthController.addCallback(mAuthControllerCallback); + mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME, + () -> new ScreenDecorCommand(mScreenDecorCommandCallback)); } /** @@ -442,7 +485,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mWindowManager = mContext.getSystemService(WindowManager.class); mContext.getDisplay().getDisplayInfo(mDisplayInfo); mRotation = mDisplayInfo.rotation; - mDisplayMode = mDisplayInfo.getMode(); + mDisplaySize.x = mDisplayInfo.getNaturalWidth(); + mDisplaySize.y = mDisplayInfo.getNaturalHeight(); mDisplayUniqueId = mDisplayInfo.uniqueId; mDisplayCutout = mDisplayInfo.displayCutout; mRoundedCornerResDelegate = @@ -463,10 +507,12 @@ public class ScreenDecorations implements CoreStartable, Dumpable { public void onDisplayChanged(int displayId) { mContext.getDisplay().getDisplayInfo(mDisplayInfo); final int newRotation = mDisplayInfo.rotation; - final Display.Mode newDisplayMode = mDisplayInfo.getMode(); if ((mOverlays != null || mScreenDecorHwcWindow != null) && (mRotation != newRotation - || displayModeChanged(mDisplayMode, newDisplayMode))) { + || displaySizeChanged(mDisplaySize, mDisplayInfo))) { + final Point newSize = new Point(); + newSize.x = mDisplayInfo.getNaturalWidth(); + newSize.y = mDisplayInfo.getNaturalHeight(); // We cannot immediately update the orientation. Otherwise // WindowManager is still deferring layout until it has finished dispatching // the config changes, which may cause divergence between what we draw @@ -478,9 +524,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { if (mRotation != newRotation) { mLogger.logRotationChangeDeferred(mRotation, newRotation); } - if (displayModeChanged(mDisplayMode, newDisplayMode)) { - mLogger.logDisplayModeChanged( - newDisplayMode.getModeId(), mDisplayMode.getModeId()); + if (!mDisplaySize.equals(newSize)) { + mLogger.logDisplaySizeChanged(mDisplaySize, newSize); } if (mOverlays != null) { @@ -489,7 +534,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { final ViewGroup overlayView = mOverlays[i].getRootView(); overlayView.getViewTreeObserver().addOnPreDrawListener( new RestartingPreDrawListener( - overlayView, i, newRotation, newDisplayMode)); + overlayView, i, newRotation, newSize)); } } } @@ -499,7 +544,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { new RestartingPreDrawListener( mScreenDecorHwcWindow, -1, // Pass -1 for views with no specific position. - newRotation, newDisplayMode)); + newRotation, newSize)); } if (mScreenDecorHwcLayer != null) { mScreenDecorHwcLayer.pendingConfigChange = true; @@ -901,15 +946,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } } - private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) { - if (oldMode == null) { - return true; - } - - // We purposely ignore refresh rate and id changes here, because we don't need to - // invalidate for those, and they can trigger the refresh rate to increase - return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth() - || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight(); + private static boolean displaySizeChanged(Point size, DisplayInfo info) { + return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight(); } private int getOverlayWindowGravity(@BoundsPosition int pos) { @@ -1128,14 +1166,14 @@ public class ScreenDecorations implements CoreStartable, Dumpable { if (mRotation != newRotation) { mDotViewController.setNewRotation(newRotation); } - final Display.Mode newMod = mDisplayInfo.getMode(); final DisplayCutout newCutout = mDisplayInfo.displayCutout; if (!mPendingConfigChange - && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod) + && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo) || !Objects.equals(newCutout, mDisplayCutout))) { mRotation = newRotation; - mDisplayMode = newMod; + mDisplaySize.x = mDisplayInfo.getNaturalWidth(); + mDisplaySize.y = mDisplayInfo.getNaturalHeight(); mDisplayCutout = newCutout; float ratio = getPhysicalPixelDisplaySizeRatio(); mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio); @@ -1228,7 +1266,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable(); } - if (topDrawable == null || bottomDrawable == null) { + if (topDrawable == null && bottomDrawable == null) { return; } mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable); @@ -1452,31 +1490,29 @@ public class ScreenDecorations implements CoreStartable, Dumpable { private final View mView; private final int mTargetRotation; - private final Display.Mode mTargetDisplayMode; + private final Point mTargetDisplaySize; // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific // position. private final int mPosition; private RestartingPreDrawListener(View view, @BoundsPosition int position, - int targetRotation, Display.Mode targetDisplayMode) { + int targetRotation, Point targetDisplaySize) { mView = view; mTargetRotation = targetRotation; - mTargetDisplayMode = targetDisplayMode; + mTargetDisplaySize = targetDisplaySize; mPosition = position; } @Override public boolean onPreDraw() { mView.getViewTreeObserver().removeOnPreDrawListener(this); - if (mTargetRotation == mRotation - && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) { + if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) { if (DEBUG_LOGGING) { final String title = mPosition < 0 ? "ScreenDecorHwcLayer" : getWindowTitleByPos(mPosition); Log.i(TAG, title + " already in target rot " + mTargetRotation + " and in target resolution " - + mTargetDisplayMode.getPhysicalWidth() + "x" - + mTargetDisplayMode.getPhysicalHeight() + + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y + ", allow draw without restarting it"); } return true; @@ -1491,8 +1527,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { : getWindowTitleByPos(mPosition); Log.i(TAG, title + " restarting listener fired, restarting draw for rot " + mRotation - + ", resolution " + mDisplayMode.getPhysicalWidth() + "x" - + mDisplayMode.getPhysicalHeight()); + + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y); } mView.invalidate(); return false; @@ -1518,19 +1553,18 @@ public class ScreenDecorations implements CoreStartable, Dumpable { public boolean onPreDraw() { mContext.getDisplay().getDisplayInfo(mDisplayInfo); final int displayRotation = mDisplayInfo.rotation; - final Display.Mode displayMode = mDisplayInfo.getMode(); - if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode)) + if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)) && !mPendingConfigChange) { if (DEBUG_LOGGING) { if (displayRotation != mRotation) { Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot " + displayRotation + ". Restarting draw"); } - if (displayModeChanged(mDisplayMode, displayMode)) { - Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth() - + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at " - + displayMode.getPhysicalWidth() + "x" - + displayMode.getPhysicalHeight() + ". Restarting draw"); + if (displaySizeChanged(mDisplaySize, mDisplayInfo)) { + Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y + + ", but display is at " + + mDisplayInfo.getNaturalWidth() + "x" + + mDisplayInfo.getNaturalHeight() + ". Restarting draw"); } } mView.invalidate(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 2a14dc894d43..1739ba4b3a01 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -28,6 +28,7 @@ import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Handler; +import android.util.SparseArray; import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManagerGlobal; @@ -72,6 +73,9 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl; private SysUiState mSysUiState; + @VisibleForTesting + SparseArray<SparseArray<Float>> mUsersScales = new SparseArray(); + private static class ControllerSupplier extends DisplayIdIndexSupplier<WindowMagnificationController> { @@ -295,6 +299,19 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback mModeSwitchesController.removeButton(displayId); } + @MainThread + void setUserMagnificationScale(int userId, int displayId, float scale) { + SparseArray<Float> scales = mUsersScales.get(userId); + if (scales == null) { + scales = new SparseArray<>(); + mUsersScales.put(userId, scales); + } + if (scales.contains(displayId) && scales.get(displayId) == scale) { + return; + } + scales.put(displayId, scale); + } + @VisibleForTesting final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java index c081893eac8b..f1d00ce218c2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java @@ -99,6 +99,12 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S } @Override + public void onUserMagnificationScaleChanged(int userId, int displayId, float scale) { + mHandler.post(() -> mWindowMagnification.setUserMagnificationScale( + userId, displayId, scale)); + } + + @Override public void setConnectionCallback(IWindowMagnificationConnectionCallback callback) { mConnectionCallback = callback; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java index 31b0f056df8d..1e1d4b7b2e5b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java @@ -172,7 +172,8 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest }; } - private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener { + private class ZoomSeekbarChangeListener implements + SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { float scale = (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE; @@ -197,6 +198,11 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest public void onStopTrackingTouch(SeekBar seekBar) { // Do nothing } + + @Override + public void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control) { + // Do nothing + } } private final AccessibilityDelegate mPanelDelegate = new AccessibilityDelegate() { @@ -521,7 +527,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0, UserHandle.USER_CURRENT); setScaleSeekbar(scale); - mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener()); + mZoomSeekbar.setOnSeekBarWithIconButtonsChangeListener(new ZoomSeekbarChangeListener()); mAllowDiagonalScrollingView = (LinearLayout) mSettingView.findViewById(R.id.magnifier_horizontal_lock_view); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt index a910ab58444d..783460c325fa 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt @@ -26,12 +26,13 @@ import android.util.TypedValue import android.view.LayoutInflater import android.widget.Button import android.widget.SeekBar -import android.widget.SeekBar.OnSeekBarChangeListener import android.widget.TextView import androidx.annotation.MainThread import androidx.annotation.WorkerThread import com.android.systemui.R import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView +import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener +import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.UserTracker @@ -105,28 +106,32 @@ class FontScalingDialog( lastProgress.set(fontSizeValueToIndex(currentScale)) seekBarWithIconButtonsView.setProgress(lastProgress.get()) - seekBarWithIconButtonsView.setOnSeekBarChangeListener( - object : OnSeekBarChangeListener { - var isTrackingTouch = false - + seekBarWithIconButtonsView.setOnSeekBarWithIconButtonsChangeListener( + object : OnSeekBarWithIconButtonsChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { // Always provide preview configuration for text first when there is a change // in the seekbar progress. createTextPreview(progress) - - if (!isTrackingTouch) { - // The seekbar progress is changed by icon buttons - changeFontSize(progress, CHANGE_BY_BUTTON_DELAY_MS) - } } override fun onStartTrackingTouch(seekBar: SeekBar) { - isTrackingTouch = true + // Do nothing } override fun onStopTrackingTouch(seekBar: SeekBar) { - isTrackingTouch = false - changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS) + // Do nothing + } + + override fun onUserInteractionFinalized( + seekBar: SeekBar, + @ControlUnitType control: Int + ) { + if (control == ControlUnitType.BUTTON) { + // The seekbar progress is changed by icon buttons + changeFontSize(seekBar.progress, CHANGE_BY_BUTTON_DELAY_MS) + } else { + changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS) + } } } ) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt index e6aeb43d9d9e..802eea300bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt @@ -37,4 +37,8 @@ class UdfpsBpViewController( dumpManager ) { override val tag = "UdfpsBpViewController" + + override fun shouldPauseAuth(): Boolean { + return false + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index dc9ba8719717..ebff0b05a52d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -85,6 +85,8 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter; +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -153,6 +155,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final SystemUIDialogManager mDialogManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; + @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @NonNull private final VibratorHelper mVibrator; @NonNull private final FeatureFlags mFeatureFlags; @NonNull private final FalsingManager mFalsingManager; @@ -272,7 +275,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { (view, event, fromUdfpsView) -> onTouch(requestId, event, fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils, - mUdfpsKeyguardAccessibilityDelegate))); + mUdfpsKeyguardAccessibilityDelegate, + mUdfpsKeyguardViewModels))); } @Override @@ -591,6 +595,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { // Pilfer if valid overlap, don't allow following events to reach keyguard shouldPilfer = true; + + // Touch is a valid UDFPS touch. Inform the falsing manager so that the touch + // isn't counted against the falsing algorithm as an accidental touch. + // We do this on the DOWN event instead of CANCEL/UP because the CANCEL/UP events + // get sent too late to this receiver (after the actual cancel/up motions occur), + // and therefore wouldn't end up being used as part of the falsing algo. + mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UP: @@ -610,7 +621,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { data.getTime(), data.getGestureStart(), mStatusBarStateController.isDozing()); - mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UNCHANGED: @@ -784,7 +794,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { private boolean shouldTryToDismissKeyguard() { return mOverlay != null && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerLegacy + instanceof UdfpsKeyguardViewControllerAdapter && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; } @@ -829,7 +839,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull InputManager inputManager, @NonNull UdfpsUtils udfpsUtils, @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, - @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate) { + @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, + @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -895,6 +906,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { return Unit.INSTANCE; }); mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; + mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider; final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController(); mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index e5421471931f..d6ef94d18e71 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -46,15 +46,19 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor -import com.android.settingslib.udfps.UdfpsUtils import com.android.settingslib.udfps.UdfpsOverlayParams +import com.android.settingslib.udfps.UdfpsUtils import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -64,6 +68,8 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.SecureSettings +import kotlinx.coroutines.ExperimentalCoroutinesApi +import javax.inject.Provider private const val TAG = "UdfpsControllerOverlay" @@ -75,6 +81,7 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" * request. This state can persist across configuration changes via the [show] and [hide] * methods. */ +@ExperimentalCoroutinesApi @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( private val context: Context, @@ -105,6 +112,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, private val udfpsUtils: UdfpsUtils, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, + private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -243,27 +251,40 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ) } REASON_AUTH_KEYGUARD -> { - UdfpsKeyguardViewControllerLegacy( - view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { - updateSensorLocation(sensorBounds) - }, - statusBarStateController, - shadeExpansionStateManager, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - dialogManager, - controller, - activityLaunchAnimator, - featureFlags, - primaryBouncerInteractor, - alternateBouncerInteractor, - udfpsKeyguardAccessibilityDelegate, - ) + if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds) + UdfpsKeyguardViewController( + view.addUdfpsView(R.layout.udfps_keyguard_view), + statusBarStateController, + shadeExpansionStateManager, + dialogManager, + dumpManager, + alternateBouncerInteractor, + udfpsKeyguardViewModels.get(), + ) + } else { + UdfpsKeyguardViewControllerLegacy( + view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { + updateSensorLocation(sensorBounds) + }, + statusBarStateController, + shadeExpansionStateManager, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dumpManager, + transitionController, + configurationController, + keyguardStateController, + unlockedScreenOffAnimationController, + dialogManager, + controller, + activityLaunchAnimator, + featureFlags, + primaryBouncerInteractor, + alternateBouncerInteractor, + udfpsKeyguardAccessibilityDelegate, + ) + } } REASON_AUTH_BP -> { // note: empty controller, currently shows no visual affordance @@ -415,7 +436,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { - if (animation !is UdfpsKeyguardViewControllerLegacy) { + if (animation !is UdfpsKeyguardViewControllerAdapter) { // always rotate view if we're not on the keyguard return true } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt new file mode 100644 index 000000000000..8cc15dadffd2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.Context +import android.util.AttributeSet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** View corresponding with udfps_keyguard_view.xml */ +@ExperimentalCoroutinesApi +class UdfpsKeyguardView( + context: Context, + attrs: AttributeSet?, +) : + UdfpsAnimationView( + context, + attrs, + ) { + private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context) + private var visible = false + + override fun calculateAlpha(): Int { + return if (mPauseAuth) { + 0 + } else 255 // ViewModels handle animating alpha values + } + + override fun getDrawable(): UdfpsDrawable { + return fingerprintDrawablePlaceHolder + } + + fun useExpandedOverlay(useExpandedOverlay: Boolean) { + mUseExpandedOverlay = useExpandedOverlay + } + + fun isVisible(): Boolean { + return visible + } + + fun setVisible(isVisible: Boolean) { + visible = isVisible + isPauseAuth = !visible + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 9bafeeca24a8..15bd73193687 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -34,6 +34,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionListener @@ -80,7 +81,8 @@ constructor( shadeExpansionStateManager, systemUIDialogManager, dumpManager, - ) { + ), + UdfpsKeyguardViewControllerAdapter { private val useExpandedOverlay: Boolean = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) private var showingUdfpsBouncer = false diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt new file mode 100644 index 000000000000..2a9f3eafc776 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui.controller + +import com.android.systemui.biometrics.UdfpsAnimationViewController +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Class that coordinates non-HBM animations during keyguard authentication. */ +@ExperimentalCoroutinesApi +open class UdfpsKeyguardViewController( + val view: UdfpsKeyguardView, + statusBarStateController: StatusBarStateController, + shadeExpansionStateManager: ShadeExpansionStateManager, + systemUIDialogManager: SystemUIDialogManager, + dumpManager: DumpManager, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + udfpsKeyguardViewModels: UdfpsKeyguardViewModels, +) : + UdfpsAnimationViewController<UdfpsKeyguardView>( + view, + statusBarStateController, + shadeExpansionStateManager, + systemUIDialogManager, + dumpManager, + ), + UdfpsKeyguardViewControllerAdapter { + override val tag: String + get() = TAG + + init { + udfpsKeyguardViewModels.bindViews(view) + } + + public override fun onViewAttached() { + super.onViewAttached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + } + + public override fun onViewDetached() { + super.onViewDetached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(false) + } + + override fun shouldPauseAuth(): Boolean { + return !view.isVisible() + } + + override fun listenForTouchesOutsideView(): Boolean { + return true + } + + companion object { + private const val TAG = "UdfpsKeyguardViewController" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index b293ea604636..db6ca0bda0f6 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -61,6 +61,7 @@ constructor( private val pin: PinBouncerViewModel by lazy { PinBouncerViewModel( + applicationContext = applicationContext, applicationScope = applicationScope, interactor = interactor, isInputEnabled = isInputEnabled, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 014ebc321d46..641e863fa303 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -16,6 +16,8 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.content.Context +import com.android.keyguard.PinShapeAdapter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import kotlinx.coroutines.CoroutineScope @@ -29,6 +31,7 @@ import kotlinx.coroutines.launch /** Holds UI state and handles user input for the PIN code bouncer UI. */ class PinBouncerViewModel( + applicationContext: Context, private val applicationScope: CoroutineScope, private val interactor: BouncerInteractor, isInputEnabled: StateFlow<Boolean>, @@ -37,6 +40,8 @@ class PinBouncerViewModel( isInputEnabled = isInputEnabled, ) { + val pinShapes = PinShapeAdapter(applicationContext) + private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList()) val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java index 4538a6ca7954..277b4274bb82 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java @@ -16,6 +16,7 @@ package com.android.systemui.common.ui.view; +import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; @@ -30,6 +31,9 @@ import android.widget.SeekBar; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * The layout contains a seekbar whose progress could be modified * through the icons on two ends of the seekbar. @@ -47,6 +51,8 @@ public class SeekBarWithIconButtonsView extends LinearLayout { private SeekBar mSeekbar; private int mSeekBarChangeMagnitude = 1; + private boolean mSetProgressFromButtonFlag = false; + private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener(); private String[] mStateLabels = null; @@ -121,21 +127,8 @@ public class SeekBarWithIconButtonsView extends LinearLayout { mSeekbar.setOnSeekBarChangeListener(mSeekBarListener); - mIconStartFrame.setOnClickListener((view) -> { - final int progress = mSeekbar.getProgress(); - if (progress > 0) { - mSeekbar.setProgress(progress - mSeekBarChangeMagnitude); - setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0); - } - }); - - mIconEndFrame.setOnClickListener((view) -> { - final int progress = mSeekbar.getProgress(); - if (progress < mSeekbar.getMax()) { - mSeekbar.setProgress(progress + mSeekBarChangeMagnitude); - setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax()); - } - }); + mIconStartFrame.setOnClickListener((view) -> onIconStartClicked()); + mIconEndFrame.setOnClickListener((view) -> onIconEndClicked()); } private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) { @@ -172,9 +165,9 @@ public class SeekBarWithIconButtonsView extends LinearLayout { * Sets a onSeekbarChangeListener to the seekbar in the layout. * We update the Start Icon and End Icon if needed when the seekbar progress is changed. */ - public void setOnSeekBarChangeListener( - @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) { - mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener); + public void setOnSeekBarWithIconButtonsChangeListener( + @Nullable OnSeekBarWithIconButtonsChangeListener onSeekBarChangeListener) { + mSeekBarListener.setOnSeekBarWithIconButtonsChangeListener(onSeekBarChangeListener); } /** @@ -216,16 +209,72 @@ public class SeekBarWithIconButtonsView extends LinearLayout { */ public void setProgress(int progress) { mSeekbar.setProgress(progress); - updateIconViewIfNeeded(progress); + updateIconViewIfNeeded(mSeekbar.getProgress()); + } + + private void setProgressFromButton(int progress) { + mSetProgressFromButtonFlag = true; + mSeekbar.setProgress(progress); + updateIconViewIfNeeded(mSeekbar.getProgress()); + } + + private void onIconStartClicked() { + final int progress = mSeekbar.getProgress(); + if (progress > 0) { + setProgressFromButton(progress - mSeekBarChangeMagnitude); + } } + private void onIconEndClicked() { + final int progress = mSeekbar.getProgress(); + if (progress < mSeekbar.getMax()) { + setProgressFromButton(progress + mSeekBarChangeMagnitude); + } + } + + /** + * Get current seekbar progress + * + * @return + */ @VisibleForTesting public int getProgress() { return mSeekbar.getProgress(); } + /** + * Extended from {@link SeekBar.OnSeekBarChangeListener} to add callback to notify the listeners + * the user interaction with the SeekBarWithIconButtonsView is finalized. + */ + public interface OnSeekBarWithIconButtonsChangeListener + extends SeekBar.OnSeekBarChangeListener { + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ControlUnitType.SLIDER, + ControlUnitType.BUTTON + }) + /** Denotes the Last user interacted control unit type. */ + @interface ControlUnitType { + int SLIDER = 0; + int BUTTON = 1; + } + + /** + * Notification that the user interaction with SeekBarWithIconButtonsView is finalized. This + * would be triggered after user ends dragging on the slider or clicks icon buttons. + * + * @param seekBar The SeekBar in which the user ends interaction with + * @param control The last user interacted control unit. It would be + * {@link ControlUnitType#SLIDER} if the user was changing the seekbar + * progress through dragging the slider, or {@link ControlUnitType#BUTTON} + * is the user was clicking button to change the progress. + */ + void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control); + } + private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener { - private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null; + private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { @@ -233,7 +282,17 @@ public class SeekBarWithIconButtonsView extends LinearLayout { setSeekbarStateDescription(); } if (mOnSeekBarChangeListener != null) { - mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); + if (mSetProgressFromButtonFlag) { + mSetProgressFromButtonFlag = false; + mOnSeekBarChangeListener.onProgressChanged( + seekBar, progress, /* fromUser= */ true); + // Directly trigger onUserInteractionFinalized since the interaction + // (click button) is ended. + mOnSeekBarChangeListener.onUserInteractionFinalized( + seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON); + } else { + mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); + } } updateIconViewIfNeeded(progress); } @@ -249,10 +308,13 @@ public class SeekBarWithIconButtonsView extends LinearLayout { public void onStopTrackingTouch(SeekBar seekBar) { if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onStopTrackingTouch(seekBar); + mOnSeekBarChangeListener.onUserInteractionFinalized( + seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER); } } - void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) { + void setOnSeekBarWithIconButtonsChangeListener( + OnSeekBarWithIconButtonsChangeListener listener) { mOnSeekBarChangeListener = listener; } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 76002d3f9693..b1f513d0945c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -42,6 +42,7 @@ import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator +import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable import com.android.systemui.power.PowerUI import com.android.systemui.reardisplay.RearDisplayDialogController import com.android.systemui.recents.Recents @@ -49,6 +50,7 @@ import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController +import com.android.systemui.statusbar.phone.LockscreenWallpaper import com.android.systemui.stylus.StylusUsiPowerStartable import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.theme.ThemeOverlayController @@ -110,6 +112,14 @@ abstract class SystemUICoreStartableModule { @ClassKey(KeyboardUI::class) abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable + /** Inject into MediaProjectionTaskSwitcherCoreStartable. */ + @Binds + @IntoMap + @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class) + abstract fun bindProjectedTaskListener( + sysui: MediaProjectionTaskSwitcherCoreStartable + ): CoreStartable + /** Inject into KeyguardBiometricLockoutLogger */ @Binds @IntoMap @@ -301,4 +311,9 @@ abstract class SystemUICoreStartableModule { @IntoMap @ClassKey(KeyguardViewConfigurator::class) abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable + + @Binds + @IntoMap + @ClassKey(LockscreenWallpaper::class) + abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 8f3c3d6e1dd5..3b897394c515 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -54,10 +54,12 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; +import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; import com.android.systemui.mediaprojection.appselector.MediaProjectionModule; +import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule; import com.android.systemui.model.SysUiState; import com.android.systemui.motiontool.MotionToolModule; import com.android.systemui.navigationbar.NavigationBarComponent; @@ -178,8 +180,10 @@ import javax.inject.Named; GarbageMonitorModule.class, KeyboardModule.class, LetterboxModule.class, + LockscreenLayoutModule.class, LogModule.class, MediaProjectionModule.class, + MediaProjectionTaskSwitcherModule.class, MotionToolModule.class, PeopleHubModule.class, PeopleModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt index 4069bc7d73d0..557168731b23 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt @@ -77,16 +77,30 @@ class DebugRoundedCornerDelegate : RoundedCornerResDelegate { } fun applyNewDebugCorners( - topCorner: DebugRoundedCornerModel, - bottomCorner: DebugRoundedCornerModel, + topCorner: DebugRoundedCornerModel?, + bottomCorner: DebugRoundedCornerModel?, ) { - hasTop = true - topRoundedDrawable = topCorner.toPathDrawable(paint) - topRoundedSize = topCorner.size() + topCorner?.let { + hasTop = true + topRoundedDrawable = it.toPathDrawable(paint) + topRoundedSize = it.size() + } + ?: { + hasTop = false + topRoundedDrawable = null + topRoundedSize = Size(0, 0) + } - hasBottom = true - bottomRoundedDrawable = bottomCorner.toPathDrawable(paint) - bottomRoundedSize = bottomCorner.size() + bottomCorner?.let { + hasBottom = true + bottomRoundedDrawable = it.toPathDrawable(paint) + bottomRoundedSize = it.size() + } + ?: { + hasBottom = false + bottomRoundedDrawable = null + bottomRoundedSize = Size(0, 0) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt new file mode 100644 index 000000000000..fa1d898de850 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.graphics.Color +import android.graphics.Path +import android.util.PathParser +import com.android.systemui.statusbar.commandline.ParseableCommand +import com.android.systemui.statusbar.commandline.Type +import com.android.systemui.statusbar.commandline.map +import java.io.PrintWriter + +/** Debug screen-decor command to be handled by the SystemUI command line interface */ +class ScreenDecorCommand( + private val callback: Callback, +) : ParseableCommand(SCREEN_DECOR_CMD_NAME) { + val debug: Boolean? by + param( + longName = "debug", + description = + "Enter or exits debug mode. Effectively makes the corners visible and allows " + + "for overriding the path data for the anti-aliasing corner paths and display " + + "cutout.", + valueParser = Type.Boolean, + ) + + val color: Int? by + param( + longName = "color", + shortName = "c", + description = + "Set a specific color for the debug assets. See Color#parseString() for " + + "accepted inputs.", + valueParser = Type.String.map { it.toColorIntOrNull() } + ) + + val roundedTop: RoundedCornerSubCommand? by subCommand(RoundedCornerSubCommand("rounded-top")) + + val roundedBottom: RoundedCornerSubCommand? by + subCommand(RoundedCornerSubCommand("rounded-bottom")) + + override fun execute(pw: PrintWriter) { + callback.onExecute(this, pw) + } + + override fun toString(): String { + return "ScreenDecorCommand(" + + "debug=$debug, " + + "color=$color, " + + "roundedTop=$roundedTop, " + + "roundedBottom=$roundedBottom)" + } + + /** For use in ScreenDecorations.java, define a Callback */ + interface Callback { + fun onExecute(cmd: ScreenDecorCommand, pw: PrintWriter) + } + + companion object { + const val SCREEN_DECOR_CMD_NAME = "screen-decor" + } +} + +/** + * Defines a subcommand suitable for `rounded-top` and `rounded-bottom`. They both have the same + * API. + */ +class RoundedCornerSubCommand(name: String) : ParseableCommand(name) { + val height by + param( + longName = "height", + description = "The height of a corner, in pixels.", + valueParser = Type.Int, + ) + .required() + + val width by + param( + longName = "width", + description = + "The width of the corner, in pixels. Likely should be equal to the height.", + valueParser = Type.Int, + ) + .required() + + val pathData by + param( + longName = "path-data", + shortName = "d", + description = + "PathParser-compatible path string to be rendered as the corner drawable. " + + "This path should be a closed arc oriented as the top-left corner " + + "of the device", + valueParser = Type.String.map { it.toPathOrNull() } + ) + .required() + + val viewportHeight: Float? by + param( + longName = "viewport-height", + description = + "The height of the viewport for the given path string. " + + "If null, the corner height will be used.", + valueParser = Type.Float, + ) + + val scaleY: Float + get() = viewportHeight?.let { height.toFloat() / it } ?: 1.0f + + val viewportWidth: Float? by + param( + longName = "viewport-width", + description = + "The width of the viewport for the given path string. " + + "If null, the corner width will be used.", + valueParser = Type.Float, + ) + + val scaleX: Float + get() = viewportWidth?.let { width.toFloat() / it } ?: 1.0f + + override fun execute(pw: PrintWriter) { + // Not needed for a subcommand + } + + override fun toString(): String { + return "RoundedCornerSubCommand(" + + "height=$height," + + " width=$width," + + " pathData='$pathData'," + + " viewportHeight=$viewportHeight," + + " viewportWidth=$viewportWidth)" + } + + fun toRoundedCornerDebugModel(): DebugRoundedCornerModel = + DebugRoundedCornerModel( + path = pathData, + width = width, + height = height, + scaleX = scaleX, + scaleY = scaleY, + ) +} + +fun String.toPathOrNull(): Path? = + try { + PathParser.createPathFromPathData(this) + } catch (e: Exception) { + null + } + +fun String.toColorIntOrNull(): Int? = + try { + Color.parseColor(this) + } catch (e: Exception) { + null + } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 94c85514f721..6abe951d7f35 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -91,6 +91,15 @@ object Flags { val NOTIFICATION_SHELF_REFACTOR = unreleasedFlag(271161129, "notification_shelf_refactor") + // TODO(b/288326013): Tracking Bug + @JvmField + val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = + unreleasedFlag( + 288326013, + "notification_async_hybrid_view_inflation", + teamfood = false + ) + @JvmField val ANIMATED_NOTIFICATION_SHADE_INSETS = releasedFlag(270682168, "animated_notification_shade_insets") @@ -251,11 +260,6 @@ object Flags { @JvmField val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true) - /** Migrate the lock icon view to the new keyguard root view. */ - // TODO(b/286552209): Tracking bug. - @JvmField - val MIGRATE_LOCK_ICON = unreleasedFlag(238, "migrate_lock_icon") - /** Whether to listen for fingerprint authentication over keyguard occluding activities. */ // TODO(b/283260512): Tracking bug. @JvmField @@ -270,6 +274,11 @@ object Flags { @JvmField val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock") + /** Migrate the lock icon view to the new keyguard root view. */ + // TODO(b/286552209): Tracking bug. + @JvmField + val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index e8881a482765..f59ad90d86ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -26,6 +26,8 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.statusbar.KeyguardIndicationController @@ -42,6 +44,8 @@ constructor( private val notificationShadeWindowView: NotificationShadeWindowView, private val featureFlags: FeatureFlags, private val indicationController: KeyguardIndicationController, + private val keyguardLayoutManager: KeyguardLayoutManager, + private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener, ) : CoreStartable { private var indicationAreaHandle: DisposableHandle? = null @@ -51,6 +55,8 @@ constructor( notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup bindIndicationArea(notificationPanel) bindLockIconView(notificationPanel) + keyguardLayoutManager.layoutViews() + keyguardLayoutManagerCommandListener.start() } fun bindIndicationArea(legacyParent: ViewGroup) { @@ -59,7 +65,7 @@ constructor( // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available. // Disable one of them if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) { - legacyParent.requireViewById<View>(R.id.keyguard_indication_area).let { + legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let { legacyParent.removeView(it) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index fdec2094e1ba..468d7606933e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -140,6 +140,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -545,6 +546,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; + private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -582,17 +585,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitching(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); - // Note that the mLockPatternUtils user has already been updated from setCurrentUser. - // We need to force a reset of the views, since lockNow (called by - // ActivityManagerService) will not reconstruct the keyguard if it is already showing. synchronized (KeyguardViewMediator.this) { resetKeyguardDonePendingLocked(); - if (mLockPatternUtils.isLockScreenDisabled(userId)) { - // If we are switching to a user that has keyguard disabled, dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } else { - resetStateLocked(); - } + dismiss(null /* callback */, null /* message */); adjustStatusBarLocked(); } } @@ -600,16 +595,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitchComplete(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - if (userId != UserHandle.USER_SYSTEM) { - UserInfo info = UserManager.get(mContext).getUserInfo(userId); - // Don't try to dismiss if the user has Pin/Pattern/Password set - if (info == null || mLockPatternUtils.isSecure(userId)) { - return; - } else if (info.isGuest() || info.isDemo()) { - // If we just switched to a guest, try to dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } - } + // We are calling dismiss again and with a delay as there are race conditions + // in some scenarios caused by async layout listeners + mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); } @Override @@ -1176,6 +1164,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, getRemoteSurfaceAlphaApplier().accept(0.0f); mDreamingToLockscreenTransitionViewModel.get() .startTransition(); + mUnoccludeFromDreamFinishedCallback = finishedCallback; return; } @@ -1255,6 +1244,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, }; } + private Consumer<TransitionStep> getFinishedCallbackConsumer() { + return (TransitionStep step) -> { + if (mUnoccludeFromDreamFinishedCallback == null) return; + try { + mUnoccludeFromDreamFinishedCallback.onAnimationFinished(); + mUnoccludeFromDreamFinishedCallback = null; + } catch (RemoteException e) { + Log.e(TAG, "Wasn't able to callback", e); + } + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); + }; + } + private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; @@ -1520,6 +1522,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, collectFlow(viewRootImpl.getView(), mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(), getRemoteSurfaceAlphaApplier(), mMainDispatcher); + collectFlow(viewRootImpl.getView(), + mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(), + getFinishedCallbackConsumer(), mMainDispatcher); } } // Most services aren't available until the system reaches the ready state, so we @@ -2382,58 +2387,72 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { @Override public void handleMessage(Message msg) { + String message = ""; switch (msg.what) { case SHOW: + message = "SHOW"; handleShow((Bundle) msg.obj); break; case HIDE: + message = "HIDE"; handleHide(); break; case RESET: + message = "RESET"; handleReset(msg.arg1 != 0); break; case VERIFY_UNLOCK: + message = "VERIFY_UNLOCK"; Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK"); handleVerifyUnlock(); Trace.endSection(); break; case NOTIFY_STARTED_GOING_TO_SLEEP: + message = "NOTIFY_STARTED_GOING_TO_SLEEP"; handleNotifyStartedGoingToSleep(); break; case NOTIFY_FINISHED_GOING_TO_SLEEP: + message = "NOTIFY_FINISHED_GOING_TO_SLEEP"; handleNotifyFinishedGoingToSleep(); break; case NOTIFY_STARTED_WAKING_UP: + message = "NOTIFY_STARTED_WAKING_UP"; Trace.beginSection( "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP"); handleNotifyStartedWakingUp(); Trace.endSection(); break; case KEYGUARD_DONE: + message = "KEYGUARD_DONE"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE"); handleKeyguardDone(); Trace.endSection(); break; case KEYGUARD_DONE_DRAWING: + message = "KEYGUARD_DONE_DRAWING"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING"); handleKeyguardDoneDrawing(); Trace.endSection(); break; case SET_OCCLUDED: + message = "SET_OCCLUDED"; Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED"); handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0); Trace.endSection(); break; case KEYGUARD_TIMEOUT: + message = "KEYGUARD_TIMEOUT"; synchronized (KeyguardViewMediator.this) { doKeyguardLocked((Bundle) msg.obj); } break; case DISMISS: - final DismissMessage message = (DismissMessage) msg.obj; - handleDismiss(message.getCallback(), message.getMessage()); + message = "DISMISS"; + final DismissMessage dismissMsg = (DismissMessage) msg.obj; + handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage()); break; case START_KEYGUARD_EXIT_ANIM: + message = "START_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); synchronized (KeyguardViewMediator.this) { @@ -2451,21 +2470,25 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.endSection(); break; case CANCEL_KEYGUARD_EXIT_ANIM: + message = "CANCEL_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM"); handleCancelKeyguardExitAnimation(); Trace.endSection(); break; case KEYGUARD_DONE_PENDING_TIMEOUT: + message = "KEYGUARD_DONE_PENDING_TIMEOUT"; Trace.beginSection("KeyguardViewMediator#handleMessage" + " KEYGUARD_DONE_PENDING_TIMEOUT"); Log.w(TAG, "Timeout while waiting for activity drawn!"); Trace.endSection(); break; case SYSTEM_READY: + message = "SYSTEM_READY"; handleSystemReady(); break; } + Log.d(TAG, "KeyguardViewMediator queue processing message: " + message); } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index b7963340228b..ed1bf3ef2753 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -167,9 +167,10 @@ constructor( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.LOCKSCREEN, animator = - getDefaultAnimatorForTransitionsToState(KeyguardState.LOCKSCREEN).apply { - duration = 0 - } + getDefaultAnimatorForTransitionsToState( + KeyguardState.LOCKSCREEN + ) + .apply { duration = 0 } ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index a8147d0c2cf6..ff0db34ca06d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -156,7 +156,12 @@ constructor( override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { - interpolator = Interpolators.LINEAR + interpolator = + when (toState) { + KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN + else -> Interpolators.LINEAR + } + duration = when (toState) { KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index d1ac49bd4409..f692a390c847 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -49,6 +49,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.TraceUtils.Companion.traceAsync import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -442,8 +443,10 @@ constructor( } private suspend fun isFeatureDisabledByDevicePolicy(): Boolean = - withContext(backgroundDispatcher) { - devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId) + traceAsync("isFeatureDisabledByDevicePolicy", TAG) { + withContext(backgroundDispatcher) { + devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId) + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index df7c79ff4264..45bf20d3ec44 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -51,11 +52,35 @@ constructor( /** (any)->GONE transition information */ val anyStateToGoneTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.GONE } + repository.transitions.filter { step -> step.to == GONE } /** (any)->AOD transition information */ val anyStateToAodTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.AOD } + repository.transitions.filter { step -> step.to == AOD } + + /** DREAMING->(any) transition information. */ + val fromDreamingTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.from == DREAMING } + + /** (any)->Lockscreen transition information */ + val anyStateToLockscreenTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == LOCKSCREEN } + + /** (any)->Occluded transition information */ + val anyStateToOccludedTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == OCCLUDED } + + /** (any)->PrimaryBouncer transition information */ + val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER } + + /** (any)->Dreaming transition information */ + val anyStateToDreamingTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == DREAMING } + + /** (any)->AlternateBouncer transition information */ + val anyStateToAlternateBouncerTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER } /** AOD->LOCKSCREEN transition information. */ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN) @@ -64,6 +89,9 @@ constructor( val dreamingToLockscreenTransition: Flow<TransitionStep> = repository.transition(DREAMING, LOCKSCREEN) + /** GONE->AOD transition information. */ + val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD) + /** GONE->DREAMING transition information. */ val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index ae6fc9e6e6dc..0dda625a8b41 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -44,9 +44,9 @@ sealed class TransitionInteractor( abstract fun start() fun startTransitionTo( - toState: KeyguardState, - animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), - resetIfCancelled: Boolean = false + toState: KeyguardState, + animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), + resetIfCancelled: Boolean = false ): UUID? { if ( fromState != transitionInteractor.startedKeyguardState.value && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt new file mode 100644 index 000000000000..bba0e37d8ed0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.animation.FloatEvaluator +import android.animation.IntEvaluator +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class UdfpsKeyguardInteractor +@Inject +constructor( + configRepo: ConfigurationRepository, + burnInInteractor: BurnInInteractor, + keyguardInteractor: KeyguardInteractor, +) { + private val intEvaluator = IntEvaluator() + private val floatEvaluator = FloatEvaluator() + + val dozeAmount = keyguardInteractor.dozeAmount + val scaleForResolution = configRepo.scaleForResolution + + /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */ + val burnInOffsets: Flow<BurnInOffsets> = + combine( + keyguardInteractor.dozeAmount, + burnInInteractor.udfpsBurnInXOffset, + burnInInteractor.udfpsBurnInYOffset, + burnInInteractor.udfpsBurnInProgress + ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> + BurnInOffsets( + intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX), + intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY), + floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress), + ) + } +} + +data class BurnInOffsets( + val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount + val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount + val burnInProgress: Float, // current progress based on the aodTransitionAmount +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt new file mode 100644 index 000000000000..ebf1beb132f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.keyguard.ui.adapter + +/** + * Temporary adapter class while + * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored + * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed. + * + * TODO (b/278719514): Delete once udfps keyguard view is fully refactored. + */ +interface UdfpsKeyguardViewControllerAdapter diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt new file mode 100644 index 000000000000..728dd3911663 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsAodFingerprintViewBinder { + + /** + * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and + * [UdfpsBackgroundViewBinder]. + */ + @JvmStatic + fun bind( + view: LottieAnimationView, + viewModel: UdfpsAodViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.burnInOffsets.collect { burnInOffsets -> + view.progress = burnInOffsets.burnInProgress + view.translationX = burnInOffsets.burnInXOffset.toFloat() + view.translationY = burnInOffsets.burnInYOffset.toFloat() + } + } + + launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } } + + launch { + viewModel.padding.collect { padding -> + view.setPadding(padding, padding, padding, padding) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt new file mode 100644 index 000000000000..26ef4685d286 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.content.res.ColorStateList +import android.widget.ImageView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsBackgroundViewBinder { + + /** + * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and + * [UdfpsFingerprintViewBinder]. + */ + @JvmStatic + fun bind( + view: ImageView, + viewModel: BackgroundViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.transition.collect { + view.alpha = it.alpha + view.scaleX = it.scale + view.scaleY = it.scale + view.imageTintList = ColorStateList.valueOf(it.color) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt new file mode 100644 index 000000000000..0ab8e52fb6c7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.model.KeyPath +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsFingerprintViewBinder { + private var udfpsIconColor = 0 + + /** + * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See + * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder]. + */ + @JvmStatic + fun bind( + view: LottieAnimationView, + viewModel: FingerprintViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.transition.collect { + view.alpha = it.alpha + view.scaleX = it.scale + view.scaleY = it.scale + if (udfpsIconColor != (it.color)) { + udfpsIconColor = it.color + view.invalidate() + } + } + } + + launch { + viewModel.burnInOffsets.collect { burnInOffsets -> + view.translationX = burnInOffsets.burnInXOffset.toFloat() + view.translationY = burnInOffsets.burnInYOffset.toFloat() + } + } + + launch { + viewModel.dozeAmount.collect { dozeAmount -> + // Lottie progress represents: aod=0 to lockscreen=1 + view.progress = 1f - dozeAmount + } + } + + launch { + viewModel.padding.collect { padding -> + view.setPadding(padding, padding, padding, padding) + } + } + } + } + + // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called + view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) { + PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt new file mode 100644 index 000000000000..b568a9af9bb8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.ui.binder + +import android.view.View +import com.android.systemui.R +import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder +import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder +import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +object UdfpsKeyguardInternalViewBinder { + + @JvmStatic + fun bind( + view: View, + viewModel: UdfpsKeyguardInternalViewModel, + aodViewModel: UdfpsAodViewModel, + fingerprintViewModel: FingerprintViewModel, + backgroundViewModel: BackgroundViewModel, + ) { + view.accessibilityDelegate = viewModel.accessibilityDelegate + + // bind child views + UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel) + UdfpsFingerprintViewBinder.bind( + view.findViewById(R.id.udfps_lockscreen_fp), + fingerprintViewModel + ) + UdfpsBackgroundViewBinder.bind( + view.findViewById(R.id.udfps_keyguard_fp_bg), + backgroundViewModel + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt new file mode 100644 index 000000000000..667abaea0b24 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.graphics.RectF +import android.view.View +import android.widget.FrameLayout +import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsKeyguardViewBinder { + /** + * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view + * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] & + * [UdfpsAodFingerprintViewBinder]. + */ + @JvmStatic + fun bind( + view: UdfpsKeyguardView, + viewModel: UdfpsKeyguardViewModel, + udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel, + aodViewModel: UdfpsAodViewModel, + fingerprintViewModel: FingerprintViewModel, + backgroundViewModel: BackgroundViewModel, + ) { + view.useExpandedOverlay(viewModel.useExpandedOverlay()) + + val layoutInflaterFinishListener = + AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent -> + UdfpsKeyguardInternalViewBinder.bind( + inflatedInternalView, + udfpsKeyguardInternalViewModel, + aodViewModel, + fingerprintViewModel, + backgroundViewModel, + ) + if (viewModel.useExpandedOverlay()) { + val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams + lp.width = viewModel.sensorBounds.width() + lp.height = viewModel.sensorBounds.height() + val relativeToView = + getBoundsRelativeToView( + inflatedInternalView, + RectF(viewModel.sensorBounds), + ) + lp.setMarginsRelative( + relativeToView.left.toInt(), + relativeToView.top.toInt(), + relativeToView.right.toInt(), + relativeToView.bottom.toInt(), + ) + parent!!.addView(inflatedInternalView, lp) + } else { + parent!!.addView(inflatedInternalView) + } + } + val inflater = AsyncLayoutInflater(view.context) + inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener) + + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + combine(aodViewModel.isVisible, fingerprintViewModel.visible) { + isAodVisible, + isFingerprintVisible -> + isAodVisible || isFingerprintVisible + } + .collect { view.setVisible(it) } + } + } + } + } + + /** + * Converts coordinates of RectF relative to the screen to coordinates relative to this view. + * + * @param bounds RectF based off screen coordinates in current orientation + */ + private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF { + val pos: IntArray = view.locationOnScreen + return RectF( + bounds.left - pos[0], + bounds.top - pos[1], + bounds.right - pos[0], + bounds.bottom - pos[1] + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt index a62f383ed704..0077f2d68c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt @@ -19,10 +19,7 @@ package com.android.systemui.keyguard.ui.view import android.content.Context import android.util.AttributeSet -import android.view.Gravity -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout import com.android.keyguard.LockIconView import com.android.systemui.R @@ -31,7 +28,7 @@ class KeyguardRootView( context: Context, private val attrs: AttributeSet?, ) : - FrameLayout( + ConstraintLayout( context, attrs, ) { @@ -43,31 +40,11 @@ class KeyguardRootView( private fun addIndicationTextArea() { val view = KeyguardIndicationArea(context, attrs) - addView( - view, - FrameLayout.LayoutParams( - MATCH_PARENT, - WRAP_CONTENT, - ) - .apply { - gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL - bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp() - } - ) + addView(view) } private fun addLockIconView() { val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view } - addView( - view, - LayoutParams( - WRAP_CONTENT, - WRAP_CONTENT, - ) - ) - } - - private fun Int.dp(): Int { - return context.resources.getDimensionPixelSize(this) + addView(view) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt new file mode 100644 index 000000000000..baaeb60f364e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.util.DisplayMetrics +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.WindowManager +import androidx.annotation.VisibleForTesting +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import javax.inject.Inject + +/** + * Positions elements of the lockscreen to the default position. + * + * This will be the most common use case for phones in portrait mode. + */ +@SysUISingleton +class DefaultLockscreenLayout +@Inject +constructor( + private val authController: AuthController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val windowManager: WindowManager, + private val context: Context, +) : LockscreenLayout { + override val id: String = DEFAULT + + override fun layoutIndicationArea(rootView: KeyguardRootView) { + val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return + + rootView.getConstraintSet().apply { + constrainWidth(indicationArea.id, MATCH_PARENT) + constrainHeight(indicationArea.id, WRAP_CONTENT) + connect( + indicationArea.id, + BOTTOM, + PARENT_ID, + BOTTOM, + R.dimen.keyguard_indication_margin_bottom.dp() + ) + connect(indicationArea.id, START, PARENT_ID, START) + connect(indicationArea.id, END, PARENT_ID, END) + applyTo(rootView) + } + } + + override fun layoutLockIcon(rootView: KeyguardRootView) { + val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported + val scaleFactor: Float = authController.scaleFactor + val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp() + val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp() + val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt() + val bounds = windowManager.currentWindowMetrics.bounds + val widthPixels = bounds.right.toFloat() + val heightPixels = bounds.bottom.toFloat() + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + + if (isUdfpsSupported) { + authController.udfpsLocation?.let { udfpsLocation -> + centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView) + } + } else { + centerLockIcon( + Point( + (widthPixels / 2).toInt(), + (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() + ), + lockIconRadiusPx * scaleFactor, + scaledPadding, + rootView + ) + } + } + + @VisibleForTesting + internal fun centerLockIcon( + center: Point, + radius: Float, + drawablePadding: Int, + rootView: KeyguardRootView, + ) { + val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return + val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return + lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding) + + val sensorRect = + Rect().apply { + set( + center.x - radius.toInt(), + center.y - radius.toInt(), + center.x + radius.toInt(), + center.y + radius.toInt(), + ) + } + + rootView.getConstraintSet().apply { + constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left) + constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top) + connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top) + connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left) + applyTo(rootView) + } + } + + private fun Int.dp(): Int { + return context.resources.getDimensionPixelSize(this) + } + + private fun ConstraintLayout.getConstraintSet(): ConstraintSet { + val cs = ConstraintSet() + cs.clone(this) + return cs + } + + companion object { + const val DEFAULT = "default" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt new file mode 100644 index 000000000000..9bc630265a3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.content.res.Configuration +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** + * Manages layout changes for the lockscreen. + * + * To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string). + */ +@SysUISingleton +class KeyguardLayoutManager +@Inject +constructor( + configurationController: ConfigurationController, + layouts: Set<@JvmSuppressWildcards LockscreenLayout>, + private val keyguardRootView: KeyguardRootView, +) { + internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id } + private var layout: LockscreenLayout? = layoutIdMap[DEFAULT] + + init { + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + layoutViews() + } + } + ) + } + + /** + * Transitions to a layout. + * + * @param layoutId + * @return whether the transition has succeeded. + */ + fun transitionToLayout(layoutId: String): Boolean { + layout = layoutIdMap[layoutId] ?: return false + layoutViews() + return true + } + + fun layoutViews() { + layout?.layoutViews(keyguardRootView) + } + + companion object { + const val TAG = "KeyguardLayoutManager" + } +} + +interface LockscreenLayout { + val id: String + + fun layoutViews(rootView: KeyguardRootView) { + // Clear constraints. + ConstraintSet() + .apply { + clone(rootView) + knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) } + } + .applyTo(rootView) + layoutIndicationArea(rootView) + layoutLockIcon(rootView) + } + fun layoutIndicationArea(rootView: KeyguardRootView) + fun layoutLockIcon(rootView: KeyguardRootView) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt new file mode 100644 index 000000000000..b351ea8923f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.view.layout + +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject + +/** Uses $ adb shell cmd statusbar layout <LayoutId> */ +class KeyguardLayoutManagerCommandListener +@Inject +constructor( + private val commandRegistry: CommandRegistry, + private val keyguardLayoutManager: KeyguardLayoutManager +) { + private val layoutCommand = KeyguardLayoutManagerCommand() + + fun start() { + commandRegistry.registerCommand(COMMAND) { layoutCommand } + } + + internal inner class KeyguardLayoutManagerCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + val arg = args.getOrNull(0) + if (arg == null || arg.lowercase() == "help") { + help(pw) + return + } + + if (keyguardLayoutManager.transitionToLayout(arg)) { + pw.println("Transition succeeded!") + } else { + pw.println("Invalid argument! To see available layout ids, run:") + pw.println("$ adb shell cmd statusbar layout help") + } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>") + pw.println("Existing Layout Ids: ") + keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") } + } + } + + companion object { + internal const val COMMAND = "layout" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt new file mode 100644 index 000000000000..00f93e33f370 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +abstract class LockscreenLayoutModule { + @Binds + @IntoSet + abstract fun bindDefaultLayout( + defaultLockscreenLayout: DefaultLockscreenLayout + ): LockscreenLayout +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index 9ca4bd62b6fe..e24d326850e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -48,7 +48,7 @@ constructor( ) val transitionEnded = - keyguardTransitionInteractor.dreamingToLockscreenTransition.filter { step -> + keyguardTransitionInteractor.fromDreamingTransition.filter { step -> step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt new file mode 100644 index 000000000000..667c2f1bd998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** View-model for UDFPS AOD view. */ +@ExperimentalCoroutinesApi +class UdfpsAodViewModel +@Inject +constructor( + val interactor: UdfpsKeyguardInteractor, + val context: Context, +) { + val alpha: Flow<Float> = interactor.dozeAmount + val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val isVisible: Flow<Boolean> = alpha.map { it != 0f } + + // Padding between the fingerprint icon and its bounding box in pixels. + val padding: Flow<Int> = + interactor.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt new file mode 100644 index 000000000000..d894a1139eeb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +class UdfpsKeyguardInternalViewModel +@Inject +constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt new file mode 100644 index 000000000000..929f27f4fea3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.graphics.Rect +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +class UdfpsKeyguardViewModel +@Inject +constructor( + private val featureFlags: FeatureFlags, +) { + var sensorBounds: Rect = Rect() + + fun useExpandedOverlay(): Boolean { + return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt new file mode 100644 index 000000000000..098b481491de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt @@ -0,0 +1,36 @@ +package com.android.systemui.keyguard.ui.viewmodel + +import android.graphics.Rect +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@SysUISingleton +class UdfpsKeyguardViewModels +@Inject +constructor( + private val viewModel: UdfpsKeyguardViewModel, + private val internalViewModel: UdfpsKeyguardInternalViewModel, + private val aodViewModel: UdfpsAodViewModel, + private val lockscreenFingerprintViewModel: FingerprintViewModel, + private val lockscreenBackgroundViewModel: BackgroundViewModel, +) { + + fun setSensorBounds(sensorBounds: Rect) { + viewModel.sensorBounds = sensorBounds + } + + fun bindViews(view: UdfpsKeyguardView) { + UdfpsKeyguardViewBinder.bind( + view, + viewModel, + internalViewModel, + aodViewModel, + lockscreenFingerprintViewModel, + lockscreenBackgroundViewModel + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt new file mode 100644 index 000000000000..fd4b666a80fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import androidx.annotation.ColorInt +import com.android.settingslib.Utils.getColorAttrDefaultColor +import com.android.systemui.R +import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** View-model for UDFPS lockscreen views. */ +@ExperimentalCoroutinesApi +open class UdfpsLockscreenViewModel( + context: Context, + lockscreenColorResId: Int, + alternateBouncerColorResId: Int, + transitionInteractor: KeyguardTransitionInteractor, +) { + private val toLockscreen: Flow<TransitionViewModel> = + transitionInteractor.anyStateToLockscreenTransition.map { + TransitionViewModel( + alpha = + if (it.from == KeyguardState.AOD) { + it.value // animate + } else { + 1f + }, + scale = 1f, + color = getColorAttrDefaultColor(context, lockscreenColorResId), + ) + } + + private val toAlternateBouncer: Flow<TransitionViewModel> = + transitionInteractor.anyStateToAlternateBouncerTransition.map { + TransitionViewModel( + alpha = 1f, + scale = + if (visibleInKeyguardState(it.from)) { + 1f + } else { + it.value + }, + color = getColorAttrDefaultColor(context, alternateBouncerColorResId), + ) + } + + private val fadeOut: Flow<TransitionViewModel> = + merge( + transitionInteractor.anyStateToGoneTransition, + transitionInteractor.anyStateToAodTransition, + transitionInteractor.anyStateToOccludedTransition, + transitionInteractor.anyStateToPrimaryBouncerTransition, + transitionInteractor.anyStateToDreamingTransition, + ) + .map { + TransitionViewModel( + alpha = + if (visibleInKeyguardState(it.from)) { + 1f - it.value + } else { + 0f + }, + scale = 1f, + color = + if (it.from == KeyguardState.ALTERNATE_BOUNCER) { + getColorAttrDefaultColor(context, alternateBouncerColorResId) + } else { + getColorAttrDefaultColor(context, lockscreenColorResId) + }, + ) + } + + private fun visibleInKeyguardState(state: KeyguardState): Boolean { + return when (state) { + KeyguardState.OFF, + KeyguardState.DOZING, + KeyguardState.DREAMING, + KeyguardState.AOD, + KeyguardState.PRIMARY_BOUNCER, + KeyguardState.GONE, + KeyguardState.OCCLUDED -> false + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER -> true + } + } + + val transition: Flow<TransitionViewModel> = + merge( + toAlternateBouncer, + toLockscreen, + fadeOut, + ) + val visible: Flow<Boolean> = transition.map { it.alpha != 0f } +} + +@ExperimentalCoroutinesApi +class FingerprintViewModel +@Inject +constructor( + val context: Context, + transitionInteractor: KeyguardTransitionInteractor, + interactor: UdfpsKeyguardInteractor, +) : + UdfpsLockscreenViewModel( + context, + android.R.attr.textColorPrimary, + com.android.internal.R.attr.materialColorOnPrimaryFixed, + transitionInteractor, + ) { + val dozeAmount: Flow<Float> = interactor.dozeAmount + val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + + // Padding between the fingerprint icon and its bounding box in pixels. + val padding: Flow<Int> = + interactor.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } +} + +@ExperimentalCoroutinesApi +class BackgroundViewModel +@Inject +constructor( + val context: Context, + transitionInteractor: KeyguardTransitionInteractor, +) : + UdfpsLockscreenViewModel( + context, + com.android.internal.R.attr.colorSurface, + com.android.internal.R.attr.materialColorPrimaryFixed, + transitionInteractor, + ) + +data class TransitionViewModel( + val alpha: Float, + val scale: Float, + @ColorInt val color: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt index 150de26c12c7..702a23ea5ebc 100644 --- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt @@ -189,15 +189,15 @@ constructor( ) } - fun logDisplayModeChanged(currentMode: Int, newMode: Int) { + fun logDisplaySizeChanged(currentSize: Point, newSize: Point) { logBuffer.log( TAG, INFO, { - int1 = currentMode - int2 = newMode + str1 = currentSize.flattenToString() + str2 = newSize.flattenToString() }, - { "Resolution changed, deferring mode change to $int2, staying at $int1" }, + { "Resolution changed, deferring size change to $str2, staying at $str1" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 30ee147e302a..2883210805d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -128,6 +128,15 @@ constructor( var visibilityChangedListener: ((Boolean) -> Unit)? = null + /** + * Whether the doze wake up animation is delayed and we are currently waiting for it to start. + */ + var isDozeWakeUpAnimationWaiting: Boolean = false + set(value) { + field = value + refreshMediaPosition() + } + /** single pane media container placed at the top of the notifications list */ var singlePaneContainer: MediaContainerView? = null private set @@ -221,7 +230,13 @@ constructor( // by the clock. This is not the case for single-line clock though. // For single shade, we don't need to do it, because media is a child of NSSL, which already // gets hidden on AOD. - return !statusBarStateController.isDozing + // Media also has to be hidden when waking up from dozing, and the doze wake up animation is + // delayed and waiting to be started. + // This is to stay in sync with the delaying of the horizontal alignment of the rest of the + // keyguard container, that is also delayed until the "wait" is over. + // If we show media during this waiting period, the shade will still be centered, and using + // the entire width of the screen, and making media show fully stretched. + return !statusBarStateController.isDozing && !isDozeWakeUpAnimationWaiting } private fun showMediaPlayer() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 318cd99a06ed..26a7d048cf27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -20,6 +20,7 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.DrawableRes; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.AnimatedVectorDrawable; @@ -181,27 +182,23 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mController.getSelectedMediaDevice(), device))); boolean isHost = device.isHostForOngoingSession() && isActiveWithOngoingSession; - if (isHost) { + if (isActiveWithOngoingSession) { mCurrentActivePosition = position; updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); mSubTitleText.setText(device.getSubtextString()); updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA); - updateEndClickAreaAsSessionEditing(device); + updateEndClickAreaAsSessionEditing(device, + isHost ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); setTwoLineLayout(device, null /* title */, true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, false /* showStatus */, true /* showEndTouchArea */, false /* isFakeActive */); initSeekbar(device, isCurrentSeekbarInvisible); } else { - if (isActiveWithOngoingSession) { - //Selected device which has ongoing session, disable seekbar since we - //only allow volume control on Host + if (currentlyConnected) { mCurrentActivePosition = position; - } - boolean showSeekbar = - (!device.hasOngoingSession() && currentlyConnected); - if (showSeekbar) { updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); initSeekbar(device, isCurrentSeekbarInvisible); @@ -222,10 +219,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateClickActionBasedOnSelectionBehavior(device) ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); setTwoLineLayout(device, currentlyConnected /* bFocused */, - showSeekbar /* showSeekBar */, + currentlyConnected /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, deviceStatusIcon != null /* showStatus */, - isActiveWithOngoingSession /* isFakeActive */); + false /* isFakeActive */); } } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); @@ -267,25 +264,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device)); } else if (device.hasOngoingSession()) { mCurrentActivePosition = position; - if (device.isHostForOngoingSession()) { - updateTitleIcon(R.drawable.media_output_icon_volume, - mController.getColorItemContent()); - updateEndClickAreaAsSessionEditing(device); - mEndClickIcon.setVisibility(View.VISIBLE); - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, - false /* showProgressBar */, false /* showCheckBox */, - true /* showEndTouchArea */); - initSeekbar(device, isCurrentSeekbarInvisible); - } else { - updateDeviceStatusIcon(mContext.getDrawable( - R.drawable.ic_sound_bars_anim)); - mStatusIcon.setVisibility(View.VISIBLE); - updateSingleLineLayoutContentAlpha( - updateClickActionBasedOnSelectionBehavior(device) - ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); - setSingleLineLayout(getItemTitle(device)); - initFakeActiveDevice(); - } + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + updateEndClickAreaAsSessionEditing(device, device.isHostForOngoingSession() + ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); + mEndClickIcon.setVisibility(View.VISIBLE); + setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + false /* showProgressBar */, false /* showCheckBox */, + true /* showEndTouchArea */); + initSeekbar(device, isCurrentSeekbarInvisible); } else if (mController.isCurrentConnectedDeviceRemote() && !mController.getSelectableMediaDevice().isEmpty()) { //If device is connected and there's other selectable devices, layout as @@ -362,7 +350,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mStatusIcon.setAlpha(alphaValue); } - private void updateEndClickAreaAsSessionEditing(MediaDevice device) { + private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) { mEndClickIcon.setOnClickListener(null); mEndTouchArea.setOnClickListener(null); updateEndClickAreaColor(mController.getColorSeekbarProgress()); @@ -371,6 +359,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mEndClickIcon.setOnClickListener( v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v)); mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick()); + Drawable drawable = mContext.getDrawable(id); + mEndClickIcon.setImageDrawable(drawable); + if (drawable instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) drawable).start(); + } } public void updateEndClickAreaColor(int color) { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt new file mode 100644 index 000000000000..3c501277ab8c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator +import javax.inject.Inject + +@SysUISingleton +class MediaProjectionTaskSwitcherCoreStartable +@Inject +constructor( + private val notificationCoordinator: TaskSwitcherNotificationCoordinator, + private val featureFlags: FeatureFlags, +) : CoreStartable { + + override fun start() { + if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) { + notificationCoordinator.start() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt new file mode 100644 index 000000000000..22ad07ebc3b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher + +import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository +import dagger.Binds +import dagger.Module + +@Module +interface MediaProjectionTaskSwitcherModule { + + @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository + + @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt new file mode 100644 index 000000000000..9938f11e5d4c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.model + +import android.app.TaskInfo + +/** Represents the state of media projection. */ +sealed interface MediaProjectionState { + object NotProjecting : MediaProjectionState + object EntireScreen : MediaProjectionState + data class SingleTask(val task: TaskInfo) : MediaProjectionState +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt new file mode 100644 index 000000000000..492d482459d6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityTaskManager +import android.app.TaskStackListener +import android.os.IBinder +import android.util.Log +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.withContext + +/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */ +@SysUISingleton +class ActivityTaskManagerTasksRepository +@Inject +constructor( + private val activityTaskManager: ActivityTaskManager, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : TasksRepository { + + override suspend fun findRunningTaskFromWindowContainerToken( + windowContainerToken: IBinder + ): RunningTaskInfo? = + getRunningTasks().firstOrNull { taskInfo -> + taskInfo.token.asBinder() == windowContainerToken + } + + private suspend fun getRunningTasks(): List<RunningTaskInfo> = + withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) } + + override val foregroundTask: Flow<RunningTaskInfo> = + conflatedCallbackFlow { + val listener = + object : TaskStackListener() { + override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) { + Log.d(TAG, "onTaskMovedToFront: $taskInfo") + trySendWithFailureLogging(taskInfo, TAG) + } + } + activityTaskManager.registerTaskStackListener(listener) + awaitClose { activityTaskManager.unregisterTaskStackListener(listener) } + } + .shareIn(applicationScope, SharingStarted.Lazily, replay = 1) + + companion object { + private const val TAG = "TasksRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt new file mode 100644 index 000000000000..38d4e698f2d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.media.projection.MediaProjectionInfo +import android.media.projection.MediaProjectionManager +import android.os.Handler +import android.util.Log +import android.view.ContentRecordingSession +import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +@SysUISingleton +class MediaProjectionManagerRepository +@Inject +constructor( + private val mediaProjectionManager: MediaProjectionManager, + @Main private val handler: Handler, + @Application private val applicationScope: CoroutineScope, + private val tasksRepository: TasksRepository, +) : MediaProjectionRepository { + + override val mediaProjectionState: Flow<MediaProjectionState> = + conflatedCallbackFlow { + val callback = + object : MediaProjectionManager.Callback() { + override fun onStart(info: MediaProjectionInfo?) { + Log.d(TAG, "MediaProjectionManager.Callback#onStart") + trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG) + } + + override fun onStop(info: MediaProjectionInfo?) { + Log.d(TAG, "MediaProjectionManager.Callback#onStop") + trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG) + } + + override fun onRecordingSessionSet( + info: MediaProjectionInfo, + session: ContentRecordingSession? + ) { + Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session") + launch { trySendWithFailureLogging(stateForSession(session), TAG) } + } + } + mediaProjectionManager.addCallback(callback, handler) + awaitClose { mediaProjectionManager.removeCallback(callback) } + } + .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1) + + private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState { + if (session == null) { + return MediaProjectionState.NotProjecting + } + if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) { + return MediaProjectionState.EntireScreen + } + val matchingTask = + tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord) + ?: return MediaProjectionState.EntireScreen + return MediaProjectionState.SingleTask(matchingTask) + } + + companion object { + private const val TAG = "MediaProjectionMngrRepo" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt new file mode 100644 index 000000000000..5bec6925babe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import kotlinx.coroutines.flow.Flow + +/** Represents a repository to retrieve and change data related to media projection. */ +interface MediaProjectionRepository { + + /** Represents the current [MediaProjectionState]. */ + val mediaProjectionState: Flow<MediaProjectionState> +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt new file mode 100644 index 000000000000..544eb6b99d4f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +/** + * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a + * placeholder, while the real implementation is not completed. + */ +@SysUISingleton +class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository { + + override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow() +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt new file mode 100644 index 000000000000..6a535e4ecc50 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.os.IBinder +import kotlinx.coroutines.flow.Flow + +/** Repository responsible for retrieving data related to running tasks. */ +interface TasksRepository { + + /** + * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when + * no matching task was found. + */ + suspend fun findRunningTaskFromWindowContainerToken( + windowContainerToken: IBinder + ): RunningTaskInfo? + + /** + * Emits a stream of [RunningTaskInfo] that have been moved to the foreground. + * + * Note: when subscribing for the first time, it will not immediately emit the current + * foreground task. Only after a change in foreground task has occurred. + */ + val foregroundTask: Flow<RunningTaskInfo> +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt new file mode 100644 index 000000000000..fc5cf7d75bdf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.domain.interactor + +import android.app.TaskInfo +import android.content.Intent +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository +import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Interactor with logic related to task switching in the context of media projection. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class TaskSwitchInteractor +@Inject +constructor( + mediaProjectionRepository: MediaProjectionRepository, + private val tasksRepository: TasksRepository, +) { + + /** + * Emits a stream of changes to the state of task switching, in the context of media projection. + */ + val taskSwitchChanges: Flow<TaskSwitchState> = + mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState -> + Log.d(TAG, "MediaProjectionState -> $projectionState") + when (projectionState) { + is MediaProjectionState.SingleTask -> { + val projectedTask = projectionState.task + tasksRepository.foregroundTask.map { foregroundTask -> + if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) { + TaskSwitchState.TaskSwitched(projectedTask, foregroundTask) + } else { + TaskSwitchState.TaskUnchanged + } + } + } + is MediaProjectionState.EntireScreen, + is MediaProjectionState.NotProjecting -> { + flowOf(TaskSwitchState.NotProjectingTask) + } + } + } + + /** + * Returns whether tasks have been switched. + * + * Always returns `false` when launcher is in the foreground. The reason is that when going to + * recents to switch apps, launcher becomes the new foreground task, and we don't want to show + * the notification then. + */ + private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) = + projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher + + private val TaskInfo.isLauncher + get() = + baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN + + companion object { + private const val TAG = "TaskSwitchInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt new file mode 100644 index 000000000000..cd1258ed6aa8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.domain.model + +import android.app.TaskInfo + +/** Represents tha state of task switching in the context of single task media projection. */ +sealed interface TaskSwitchState { + /** Currently no task is being projected. */ + object NotProjectingTask : TaskSwitchState + /** The foreground task is the same as the task that is currently being projected. */ + object TaskUnchanged : TaskSwitchState + /** The foreground task is a different one to the task it currently being projected. */ + data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) : + TaskSwitchState +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt new file mode 100644 index 000000000000..a4f407612fa8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.ui + +import android.content.Context +import android.util.Log +import android.widget.Toast +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing +import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.launch + +/** Coordinator responsible for showing/hiding the task switcher notification. */ +@SysUISingleton +class TaskSwitcherNotificationCoordinator +@Inject +constructor( + private val context: Context, + @Application private val applicationScope: CoroutineScope, + @Main private val mainDispatcher: CoroutineDispatcher, + private val viewModel: TaskSwitcherNotificationViewModel, +) { + + fun start() { + applicationScope.launch { + viewModel.uiState.flowOn(mainDispatcher).collect { uiState -> + Log.d(TAG, "uiState -> $uiState") + when (uiState) { + is Showing -> showNotification(uiState) + is NotShowing -> hideNotification() + } + } + } + } + + private fun showNotification(uiState: Showing) { + val text = + """ + Sharing pauses when you switch apps. + Share this app instead. + Switch back. + """ + .trimIndent() + // TODO(b/286201515): Create actual notification. + Toast.makeText(context, text, Toast.LENGTH_SHORT).show() + } + + private fun hideNotification() {} + + companion object { + private const val TAG = "TaskSwitchNotifCoord" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt new file mode 100644 index 000000000000..21aee72d17ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.ui.model + +import android.app.TaskInfo + +/** Represents the UI state for the task switcher notification. */ +sealed interface TaskSwitcherNotificationUiState { + /** The notification should not be shown. */ + object NotShowing : TaskSwitcherNotificationUiState + /** The notification should be shown. */ + data class Showing( + val projectedTask: TaskInfo, + val foregroundTask: TaskInfo, + ) : TaskSwitcherNotificationUiState +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt new file mode 100644 index 000000000000..d9754d4429d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel + +import android.util.Log +import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor +import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) { + + val uiState: Flow<TaskSwitcherNotificationUiState> = + interactor.taskSwitchChanges.map { taskSwitchChange -> + Log.d(TAG, "taskSwitchChange: $taskSwitchChange") + when (taskSwitchChange) { + is TaskSwitchState.TaskSwitched -> { + TaskSwitcherNotificationUiState.Showing( + projectedTask = taskSwitchChange.projectedTask, + foregroundTask = taskSwitchChange.foregroundTask, + ) + } + is TaskSwitchState.NotProjectingTask, + is TaskSwitchState.TaskUnchanged -> { + TaskSwitcherNotificationUiState.NotShowing + } + } + } + + companion object { + private const val TAG = "TaskSwitchNotifVM" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java index 63276fee811b..99daf368c846 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java @@ -28,18 +28,21 @@ import android.content.res.ApkAssets; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Secure; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dump.DumpManager; -import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.io.PrintWriter; import java.util.ArrayList; @@ -64,22 +67,21 @@ public class NavigationModeController implements Dumpable { private Context mCurrentUserContext; private final IOverlayManager mOverlayManager; private final Executor mUiBgExecutor; + private final UserTracker mUserTracker; private ArrayList<ModeChangedListener> mListeners = new ArrayList<>(); - private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback = - new DeviceProvisionedController.DeviceProvisionedListener() { - @Override - public void onUserSwitched() { - if (DEBUG) { - Log.d(TAG, "onUserSwitched: " - + ActivityManagerWrapper.getInstance().getCurrentUserId()); - } - - // Update the nav mode for the current user - updateCurrentInteractionMode(true /* notify */); - } - }; + private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + if (DEBUG) { + Log.d(TAG, "onUserChanged: " + + newUser); + } + + updateCurrentInteractionMode(true /* notify */); + } + }; // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for // the secondary user (b/158613864), so we need to update the interaction mode here as well @@ -97,19 +99,20 @@ public class NavigationModeController implements Dumpable { @Inject public NavigationModeController(Context context, - DeviceProvisionedController deviceProvisionedController, ConfigurationController configurationController, + UserTracker userTracker, + @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, DumpManager dumpManager) { mContext = context; mCurrentUserContext = context; + mUserTracker = userTracker; + mUserTracker.addCallback(mUserTrackerCallback, mainExecutor); mOverlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); mUiBgExecutor = uiBgExecutor; dumpManager.registerDumpable(getClass().getSimpleName(), this); - deviceProvisionedController.addCallback(mDeviceProvisionedCallback); - IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); overlayFilter.addDataScheme("package"); overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); @@ -129,6 +132,7 @@ public class NavigationModeController implements Dumpable { } public void updateCurrentInteractionMode(boolean notify) { + Trace.beginSection("NMC#updateCurrentInteractionMode"); mCurrentUserContext = getCurrentUserContext(); int mode = getCurrentInteractionMode(mCurrentUserContext); mUiBgExecutor.execute(() -> @@ -144,6 +148,7 @@ public class NavigationModeController implements Dumpable { mListeners.get(i).onNavigationModeChanged(mode); } } + Trace.endSection(); } public int addListener(ModeChangedListener listener) { @@ -171,7 +176,7 @@ public class NavigationModeController implements Dumpable { } public Context getCurrentUserContext() { - int userId = ActivityManagerWrapper.getInstance().getCurrentUserId(); + int userId = mUserTracker.getUserId(); if (DEBUG) { Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId() + " currentUser=" + userId); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 2a9e7d05c187..1ca2a961744b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -330,7 +330,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy final int eventId = mClickEventId++; mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state, eventId); - mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); + if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { + mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); + } } public LogMaker populate(LogMaker logMaker) { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 8879501fa03d..5199bd43f982 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -84,6 +84,7 @@ public class BrightnessDialog extends Activity { window.getDecorView(); window.setLayout( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); + getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); setContentView(R.layout.brightness_mirror_container); FrameLayout frame = findViewById(R.id.brightness_mirror_container); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 5c776f587820..d97db3b27c87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -117,6 +117,7 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeLog; @@ -208,7 +209,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; @@ -238,7 +238,7 @@ import kotlin.Unit; import kotlinx.coroutines.CoroutineDispatcher; -@CentralSurfacesComponent.CentralSurfacesScope +@SysUISingleton public final class NotificationPanelViewController implements ShadeSurface, Dumpable { public static final String TAG = NotificationPanelView.class.getSimpleName(); @@ -1175,6 +1175,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewComponentFactory.build(keyguardStatusView); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); updateClockAppearance(); if (mKeyguardUserSwitcherController != null) { @@ -1227,6 +1228,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onSplitShadeEnabledChanged() { mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); @@ -1407,11 +1409,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardBottomArea = keyguardBottomArea; } - void setOpenCloseListener(OpenCloseListener openCloseListener) { + @Override + public void setOpenCloseListener(OpenCloseListener openCloseListener) { mOpenCloseListener = openCloseListener; } - void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { + @Override + public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { mTrackingStartedListener = trackingStartedListener; } @@ -1623,6 +1627,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mWillPlayDelayedDozeAmountAnimation = willPlay; mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay); + mKeyguardMediaController.setDozeWakeUpAnimationWaiting(willPlay); // Once changing this value, see if we should move the clock. positionClockAndNotifications(); @@ -3378,11 +3383,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ViewGroupFadeHelper.reset(mView); } - void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + @Override + public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().addOnGlobalLayoutListener(listener); } - void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + @Override + public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener); } @@ -3852,8 +3859,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return !isFullyCollapsed() && !mTracking && !mClosing; } - /** Collapses the shade instantly without animation. */ - void instantCollapse() { + @Override + public void instantCollapse() { abortAnimations(); setExpandedFraction(0f); if (mExpanding) { @@ -4026,8 +4033,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mFixedDuration = NO_FIXED_DURATION; } - /** */ - boolean postToView(Runnable action) { + @Override + public boolean postToView(Runnable action) { return mView.post(action); } @@ -5122,18 +5129,5 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return super.performAccessibilityAction(host, action, args); } } - - /** Listens for when touch tracking begins. */ - interface TrackingStartedListener { - void onTrackingStarted(); - } - - /** Listens for when shade begins opening of finishes closing. */ - interface OpenCloseListener { - /** Called when the shade finishes closing. */ - void onClosingFinished(); - /** Called when the shade starts opening. */ - void onOpenStarted(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 1361c9f25eff..025c461110ef 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -68,6 +68,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; @@ -98,7 +99,6 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.LargeScreenUtils; @@ -113,7 +113,7 @@ import javax.inject.Inject; /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ -@CentralSurfacesComponent.CentralSurfacesScope +@SysUISingleton public class QuickSettingsController implements Dumpable { public static final String TAG = "QuickSettingsController"; @@ -1220,14 +1220,15 @@ public class QuickSettingsController implements Dumpable { if (mIsFullWidth) { clipStatusView = qsVisible; float screenCornerRadius = - !mSplitShadeEnabled || mRecordingController.isRecording() - || mCastController.hasConnectedCastDevice() + mRecordingController.isRecording() || mCastController.hasConnectedCastDevice() ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); - float bottomRadius = mExpanded ? screenCornerRadius : - calculateBottomCornerRadius(screenCornerRadius); + float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0; + if (!mExpanded) { + bottomRadius = calculateBottomCornerRadius(bottomRadius); + } mScrimController.setNotificationBottomRadius(bottomRadius); } if (isQsFragmentCreated()) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index 9ed0e9a8b359..317d88585958 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -165,8 +165,7 @@ public interface ShadeController { NotificationShadeWindowViewController notificationShadeWindowViewController); /** */ - void setNotificationPanelViewController( - NotificationPanelViewController notificationPanelViewController); + void setShadeViewController(ShadeViewController shadeViewController); /** Listens for shade visibility changes. */ interface ShadeVisibilityListener { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index c9338b3614ea..b92afac047fa 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -70,7 +70,8 @@ public final class ShadeControllerImpl implements ShadeController { private boolean mExpandedVisible; - private NotificationPanelViewController mNotificationPanelViewController; + // TODO(b/237661616): Rename this variable to mShadeViewController. + private ShadeViewController mNotificationPanelViewController; private NotificationPresenter mPresenter; private NotificationShadeWindowViewController mNotificationShadeWindowViewController; private ShadeVisibilityListener mShadeVisibilityListener; @@ -426,12 +427,11 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public void setNotificationPanelViewController( - NotificationPanelViewController notificationPanelViewController) { - mNotificationPanelViewController = notificationPanelViewController; + public void setShadeViewController(ShadeViewController shadeViewController) { + mNotificationPanelViewController = shadeViewController; mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables); mNotificationPanelViewController.setOpenCloseListener( - new NotificationPanelViewController.OpenCloseListener() { + new OpenCloseListener() { @Override public void onClosingFinished() { ShadeControllerImpl.this.onClosingFinished(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 0154b493bd0c..8ae9e5e1fb8c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -72,6 +72,12 @@ abstract class ShadeModule { @ClassKey(AuthRippleController::class) abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable + @Binds + @SysUISingleton + abstract fun bindsShadeViewController( + notificationPanelViewController: NotificationPanelViewController + ): ShadeViewController + companion object { const val SHADE_HEADER = "large_screen_shade_header" @@ -209,9 +215,15 @@ abstract class ShadeModule { @Provides @SysUISingleton fun providesLockIconView( - notificationShadeWindowView: NotificationShadeWindowView, + keyguardRootView: KeyguardRootView, + notificationPanelView: NotificationPanelView, + featureFlags: FeatureFlags ): LockIconView { - return notificationShadeWindowView.findViewById(R.id.lock_icon_view) + if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + return keyguardRootView.findViewById(R.id.lock_icon_view) + } else { + return notificationPanelView.findViewById(R.id.lock_icon_view) + } } // TODO(b/277762009): Only allow this view's controller to inject the view. See above. diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 3d9fcf9cdecb..9aa5eb0cd68b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.view.MotionEvent import android.view.ViewGroup +import android.view.ViewTreeObserver import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.statusbar.RemoteInputController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -77,6 +78,9 @@ interface ShadeViewController { /** Collapses the shade with an animation duration in milliseconds. */ fun collapseWithDuration(animationDuration: Int) + /** Collapses the shade instantly without animation. */ + fun instantCollapse() + /** * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If * in split shade, it will collapse the whole shade. @@ -100,6 +104,9 @@ interface ShadeViewController { /** Returns whether the shade's top level view is enabled. */ val isViewEnabled: Boolean + /** Sets a listener to be notified when the shade starts opening or finishes closing. */ + fun setOpenCloseListener(openCloseListener: OpenCloseListener) + /** Returns whether status bar icons should be hidden when the shade is expanded. */ fun shouldHideStatusBarIconsWhenExpanded(): Boolean @@ -109,6 +116,9 @@ interface ShadeViewController { */ fun blockExpansionForCurrentTouch() + /** Sets a listener to be notified when touch tracking begins. */ + fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) + /** * Disables the shade header. * @@ -178,6 +188,15 @@ interface ShadeViewController { /** Ensures that the touchable region is updated. */ fun updateTouchableRegion() + /** Adds a global layout listener. */ + fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) + + /** Removes a global layout listener. */ + fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) + + /** Posts the given runnable to the view. */ + fun postToView(action: Runnable): Boolean + // ******* Begin Keyguard Section ********* /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */ fun transitionToExpandedShade(delay: Long) @@ -337,3 +356,17 @@ interface ShadeViewStateProvider { /** Return the fraction of the shade that's expanded, when in lockscreen. */ val lockscreenShadeDragProgress: Float } + +/** Listens for when touch tracking begins. */ +interface TrackingStartedListener { + fun onTrackingStarted() +} + +/** Listens for when shade begins opening or finishes closing. */ +interface OpenCloseListener { + /** Called when the shade finishes closing. */ + fun onClosingFinished() + + /** Called when the shade starts opening. */ + fun onOpenStarted() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt new file mode 100644 index 000000000000..de369c35345c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +/** + * [CommandParser] defines the collection of tokens which can be parsed from an incoming command + * list, and parses them into their respective containers. Supported tokens are of the following + * forms: + * ``` + * Flag: boolean value, false by default. always optional. + * Param: named parameter, taking N args all of a given type. Currently only single arg parameters + * are supported. + * SubCommand: named command created by adding a command to a parent. Supports all fields above, but + * not other subcommands. + * ``` + * + * Tokens are added via the factory methods for each token type. They can be made `required` by + * calling the [require] method for the appropriate type, as follows: + * ``` + * val requiredParam = parser.require(parser.param(...)) + * ``` + * + * The reason for having an explicit require is so that generic type arguments can be handled + * properly. See [SingleArgParam] and [SingleArgParamOptional] for the difference between an + * optional parameter and a required one. + * + * Typical usage of a required parameter, however, will occur within the context of a + * [ParseableCommand], which defines a convenience `require()` method: + * ``` + * class MyCommand : ParseableCommand { + * val requiredParam = param(...).require() + * } + * ``` + * + * This parser defines two modes of parsing, both of which validate for required parameters. + * 1. [parse] is a top-level parsing method. This parser will walk the given arg list and populate + * all of the delegate classes based on their type. It will handle SubCommands, and after parsing + * will check for any required-but-missing SubCommands or Params. + * + * **This method requires that every received token is represented in its grammar.** + * 2. [parseAsSubCommand] is a second-level parsing method suitable for any [SubCommand]. This + * method will handle _only_ flags and params. It will return parsing control to its parent + * parser on the first unknown token rather than throwing. + */ +class CommandParser { + private val _flags = mutableListOf<Flag>() + val flags: List<Flag> = _flags + private val _params = mutableListOf<Param>() + val params: List<Param> = _params + private val _subCommands = mutableListOf<SubCommand>() + val subCommands: List<SubCommand> = _subCommands + + private val tokenSet = mutableSetOf<String>() + + /** + * Parse the arg list into the fields defined in the containing class. + * + * @return true if all required fields are present after parsing + * @throws ArgParseError on any failure to process args + */ + fun parse(args: List<String>): Boolean { + if (args.isEmpty()) { + return false + } + + val iterator = args.listIterator() + var tokenHandled: Boolean + while (iterator.hasNext()) { + val token = iterator.next() + tokenHandled = false + + flags + .find { it.matches(token) } + ?.let { + it.inner = true + tokenHandled = true + } + + if (tokenHandled) continue + + params + .find { it.matches(token) } + ?.let { + it.parseArgsFromIter(iterator) + tokenHandled = true + } + + if (tokenHandled) continue + + subCommands + .find { it.matches(token) } + ?.let { + it.parseSubCommandArgs(iterator) + tokenHandled = true + } + + if (!tokenHandled) { + throw ArgParseError("Unknown token: $token") + } + } + + return validateRequiredParams() + } + + /** + * Parse a subset of the commands that came in from the top-level [parse] method, for the + * subcommand that this parser represents. Note that subcommands may not contain other + * subcommands. But they may contain flags and params. + * + * @return true if all required fields are present after parsing + * @throws ArgParseError on any failure to process args + */ + fun parseAsSubCommand(iter: ListIterator<String>): Boolean { + // arg[-1] is our subcommand name, so the rest of the args are either for this + // subcommand, OR for the top-level command to handle. Therefore, we bail on the first + // failure, but still check our own required params + + // The mere presence of a subcommand (similar to a flag) is a valid subcommand + if (flags.isEmpty() && params.isEmpty()) { + return validateRequiredParams() + } + + var tokenHandled: Boolean + while (iter.hasNext()) { + val token = iter.next() + tokenHandled = false + + flags + .find { it.matches(token) } + ?.let { + it.inner = true + tokenHandled = true + } + + if (tokenHandled) continue + + params + .find { it.matches(token) } + ?.let { + it.parseArgsFromIter(iter) + tokenHandled = true + } + + if (!tokenHandled) { + // Move the cursor position backwards since we've arrived at a token + // that we don't own + iter.previous() + break + } + } + + return validateRequiredParams() + } + + /** + * If [parse] or [parseAsSubCommand] does not produce a valid result, generate a list of errors + * based on missing elements + */ + fun generateValidationErrorMessages(): List<String> { + val missingElements = mutableListOf<String>() + + if (unhandledParams.isNotEmpty()) { + val names = unhandledParams.map { it.longName } + missingElements.add("No values passed for required params: $names") + } + + if (unhandledSubCmds.isNotEmpty()) { + missingElements.addAll(unhandledSubCmds.map { it.longName }) + val names = unhandledSubCmds.map { it.shortName } + missingElements.add("No values passed for required sub-commands: $names") + } + + return missingElements + } + + /** Check for any missing, required params, or any invalid subcommands */ + private fun validateRequiredParams(): Boolean = + unhandledParams.isEmpty() && unhandledSubCmds.isEmpty() && unvalidatedSubCmds.isEmpty() + + // If any required param (aka non-optional) hasn't handled a field, then return false + private val unhandledParams: List<Param> + get() = params.filter { (it is SingleArgParam<*>) && !it.handled } + + private val unhandledSubCmds: List<SubCommand> + get() = subCommands.filter { (it is RequiredSubCommand<*> && !it.handled) } + + private val unvalidatedSubCmds: List<SubCommand> + get() = subCommands.filter { !it.validationStatus } + + private fun checkCliNames(short: String?, long: String): String? { + if (short != null && tokenSet.contains(short)) { + return short + } + + if (tokenSet.contains(long)) { + return long + } + + return null + } + + private fun subCommandContainsSubCommands(cmd: ParseableCommand): Boolean = + cmd.parser.subCommands.isNotEmpty() + + private fun registerNames(short: String?, long: String) { + if (short != null) { + tokenSet.add(short) + } + tokenSet.add(long) + } + + /** + * Turns a [SingleArgParamOptional]<T> into a [SingleArgParam] by converting the [T?] into [T] + * + * @return a [SingleArgParam] property delegate + */ + fun <T : Any> require(old: SingleArgParamOptional<T>): SingleArgParam<T> { + val newParam = + SingleArgParam( + longName = old.longName, + shortName = old.shortName, + description = old.description, + valueParser = old.valueParser, + ) + + replaceWithRequired(old, newParam) + return newParam + } + + private fun <T : Any> replaceWithRequired( + old: SingleArgParamOptional<T>, + new: SingleArgParam<T>, + ) { + _params.remove(old) + _params.add(new) + } + + /** + * Turns an [OptionalSubCommand] into a [RequiredSubCommand] by converting the [T?] in to [T] + * + * @return a [RequiredSubCommand] property delegate + */ + fun <T : ParseableCommand> require(optional: OptionalSubCommand<T>): RequiredSubCommand<T> { + val newCmd = RequiredSubCommand(optional.cmd) + replaceWithRequired(optional, newCmd) + return newCmd + } + + private fun <T : ParseableCommand> replaceWithRequired( + old: OptionalSubCommand<T>, + new: RequiredSubCommand<T>, + ) { + _subCommands.remove(old) + _subCommands.add(new) + } + + internal fun flag( + longName: String, + shortName: String? = null, + description: String = "", + ): Flag { + checkCliNames(shortName, longName)?.let { + throw IllegalArgumentException("Detected reused flag name ($it)") + } + registerNames(shortName, longName) + + val flag = Flag(shortName, longName, description) + _flags.add(flag) + return flag + } + + internal fun <T : Any> param( + longName: String, + shortName: String? = null, + description: String = "", + valueParser: ValueParser<T>, + ): SingleArgParamOptional<T> { + checkCliNames(shortName, longName)?.let { + throw IllegalArgumentException("Detected reused param name ($it)") + } + registerNames(shortName, longName) + + val param = + SingleArgParamOptional( + shortName = shortName, + longName = longName, + description = description, + valueParser = valueParser, + ) + _params.add(param) + return param + } + + internal fun <T : ParseableCommand> subCommand( + command: T, + ): OptionalSubCommand<T> { + checkCliNames(null, command.name)?.let { + throw IllegalArgumentException("Cannot re-use name for subcommand ($it)") + } + + if (subCommandContainsSubCommands(command)) { + throw IllegalArgumentException( + "SubCommands may not contain other SubCommands. $command" + ) + } + + registerNames(null, command.name) + + val subCmd = OptionalSubCommand(command) + _subCommands.add(subCmd) + return subCmd + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt new file mode 100644 index 000000000000..6ed5eed79c82 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import android.util.IndentingPrintWriter +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Definitions for all parameter types usable by [ParseableCommand]. Parameters are command line + * tokens that accept a fixed number of arguments and convert them to a parsed type. + * + * Example: + * ``` + * my_command --single-arg-param arg + * ``` + * + * In the example, `my_command` is the name of the command, `--single-arg-param` is the parameter, + * and `arg` is the value parsed by that parameter into its eventual type. + * + * Note on generics: The intended usage for parameters is to be able to return the parsed type from + * the given command as a `val` via property delegation. For example, let's say we have a command + * that has one optional and one required parameter: + * ``` + * class MyCommand : ParseableCommand { + * val requiredParam: Int by parser.param(...).required() + * val optionalParam: Int? by parser.param(...) + * } + * ``` + * + * In order to make the simple `param` method return the correct type, we need to do two things: + * 1. Break out the generic type into 2 pieces (TParsed and T) + * 2. Create two different underlying Parameter subclasses to handle the property delegation. One + * handles `T?` and the other handles `T`. Note that in both cases, `TParsed` is always non-null + * since the value parsed from the argument will throw an exception if missing or if it cannot be + * parsed. + */ + +/** A param type knows the number of arguments it expects */ +sealed interface Param : Describable { + val numArgs: Int + + /** + * Consume [numArgs] items from the iterator and relay the result into its corresponding + * delegated type. + */ + fun parseArgsFromIter(iterator: Iterator<String>) +} + +/** + * Base class for required and optional SingleArgParam classes. For convenience, UnaryParam is + * defined as a [MultipleArgParam] where numArgs = 1. The benefit is that we can define the parsing + * in a single place, and yet on the client side we can unwrap the underlying list of params + * automatically. + */ +abstract class UnaryParamBase<out T, out TParsed : T>(val wrapped: MultipleArgParam<T, TParsed>) : + Param, ReadOnlyProperty<Any?, T> { + var handled = false + + override fun describe(pw: IndentingPrintWriter) { + if (shortName != null) { + pw.print("$shortName, ") + } + pw.print(longName) + pw.println(" ${typeDescription()}") + if (description != null) { + pw.indented { pw.println(description) } + } + } + + /** + * Try to describe the arg type. We can know if it's one of the base types what kind of input it + * takes. Otherwise just print "<arg>" and let the clients describe in the help text + */ + private fun typeDescription() = + when (wrapped.valueParser) { + Type.Int -> "<int>" + Type.Float -> "<float>" + Type.String -> "<string>" + Type.Boolean -> "<boolean>" + else -> "<arg>" + } +} + +/** Required single-arg parameter, delegating a non-null type to the client. */ +class SingleArgParam<out T : Any>( + override val longName: String, + override val shortName: String? = null, + override val description: String? = null, + val valueParser: ValueParser<T>, +) : + UnaryParamBase<T, T>( + MultipleArgParam( + longName, + shortName, + 1, + description, + valueParser, + ) + ) { + + override fun getValue(thisRef: Any?, property: KProperty<*>): T = + if (handled) { + wrapped.getValue(thisRef, property)[0] + } else { + throw IllegalStateException("Attempt to read property before parse() has executed") + } + + override val numArgs: Int = 1 + + override fun parseArgsFromIter(iterator: Iterator<String>) { + wrapped.parseArgsFromIter(iterator) + handled = true + } +} + +/** Optional single-argument parameter, delegating a nullable type to the client. */ +class SingleArgParamOptional<out T : Any>( + override val longName: String, + override val shortName: String? = null, + override val description: String? = null, + val valueParser: ValueParser<T>, +) : + UnaryParamBase<T?, T>( + MultipleArgParam( + longName, + shortName, + 1, + description, + valueParser, + ) + ) { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = + wrapped.getValue(thisRef, property).getOrNull(0) + + override val numArgs: Int = 1 + + override fun parseArgsFromIter(iterator: Iterator<String>) { + wrapped.parseArgsFromIter(iterator) + handled = true + } +} + +/** + * Parses a list of args into the underlying [T] data type. The resultant value is an ordered list + * of type [TParsed]. + * + * [T] and [TParsed] are split out here in the case where the entire param is optional. I.e., a + * MultipleArgParam<T?, T> indicates a command line argument that can be omitted. In that case, the + * inner list is List<T>?, NOT List<T?>. If the argument is provided, then the type is always going + * to be parsed into T rather than T?. + */ +class MultipleArgParam<out T, out TParsed : T>( + override val longName: String, + override val shortName: String? = null, + override val numArgs: Int = 1, + override val description: String? = null, + val valueParser: ValueParser<TParsed>, +) : ReadOnlyProperty<Any?, List<TParsed>>, Param { + private val inner: MutableList<TParsed> = mutableListOf() + + override fun getValue(thisRef: Any?, property: KProperty<*>): List<TParsed> = inner + + /** + * Consumes [numArgs] values of the iterator and parses them into [TParsed]. + * + * @throws ArgParseError on the first failure + */ + override fun parseArgsFromIter(iterator: Iterator<String>) { + if (!iterator.hasNext()) { + throw ArgParseError("no argument provided for $shortName") + } + for (i in 0 until numArgs) { + valueParser + .parseValue(iterator.next()) + .fold(onSuccess = { inner.add(it) }, onFailure = { throw it }) + } + } +} + +data class ArgParseError(override val message: String) : Exception(message) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt new file mode 100644 index 000000000000..ecd3fa6cc299 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import android.util.IndentingPrintWriter +import java.io.PrintWriter +import java.lang.IllegalArgumentException +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * An implementation of [Command] that includes a [CommandParser] which can set all delegated + * properties. + * + * As the number of registrants to [CommandRegistry] grows, we should have a default mechanism for + * parsing common command line arguments. We are not expecting to build an arbitrarily-functional + * CLI, nor a GNU arg parse compliant interface here, we simply want to be able to empower clients + * to create simple CLI grammars such as: + * ``` + * $ my_command [-f|--flag] + * $ my_command [-a|--arg] <params...> + * $ my_command [subcommand1] [subcommand2] + * $ my_command <positional_arg ...> # not-yet implemented + * ``` + * + * Note that the flags `-h` and `--help` are reserved for the base class. It seems prudent to just + * avoid them in your implementation. + * + * Usage: + * + * The intended usage tries to be clever enough to enable good ergonomics, while not too clever as + * to be unmaintainable. Using the default parser is done using property delegates, and looks like: + * ``` + * class MyCommand( + * onExecute: (cmd: MyCommand, pw: PrintWriter) -> () + * ) : ParseableCommand(name) { + * val flag1 by flag( + * shortName = "-f", + * longName = "--flag", + * required = false, + * ) + * val param1: String by param( + * shortName = "-a", + * longName = "--args", + * valueParser = Type.String + * ).required() + * val param2: Int by param(..., valueParser = Type.Int) + * val subCommand by subCommand(...) + * + * override fun execute(pw: PrintWriter) { + * onExecute(this, pw) + * } + * + * companion object { + * const val name = "my_command" + * } + * } + * + * fun main() { + * fun printArgs(cmd: MyCommand, pw: PrintWriter) { + * pw.println("${cmd.flag1}") + * pw.println("${cmd.param1}") + * pw.println("${cmd.param2}") + * pw.println("${cmd.subCommand}") + * } + * + * commandRegistry.registerCommand(MyCommand.companion.name) { + * MyCommand() { (cmd, pw) -> + * printArgs(cmd, pw) + * } + * } + * } + * + * ``` + */ +abstract class ParseableCommand(val name: String, val description: String? = null) : Command { + val parser: CommandParser = CommandParser() + + val help by flag(longName = "help", shortName = "h", description = "Print help and return") + + /** + * After [execute(pw, args)] is called, this class goes through a parsing stage and sets all + * delegated properties. It is safe to read any delegated properties here. + * + * This method is never called for [SubCommand]s, since they are associated with a top-level + * command that handles [execute] + */ + abstract fun execute(pw: PrintWriter) + + /** + * Given a command string list, [execute] parses the incoming command and validates the input. + * If this command or any of its subcommands is passed `-h` or `--help`, then execute will only + * print the relevant help message and exit. + * + * If any error is thrown during parsing, we will catch and log the error. This process should + * _never_ take down its process. Override [onParseFailed] to handle an [ArgParseError]. + * + * Important: none of the delegated fields can be read before this stage. + */ + override fun execute(pw: PrintWriter, args: List<String>) { + val success: Boolean + try { + success = parser.parse(args) + } catch (e: ArgParseError) { + pw.println(e.message) + onParseFailed(e) + return + } catch (e: Exception) { + pw.println("Unknown exception encountered during parse") + pw.println(e) + return + } + + // Now we've parsed the incoming command without error. There are two things to check: + // 1. If any help is requested, print the help message and return + // 2. Otherwise, make sure required params have been passed in, and execute + + val helpSubCmds = subCmdsRequestingHelp() + + // Top-level help encapsulates subcommands. Otherwise, if _any_ subcommand requests + // help then defer to them. Else, just execute + if (help) { + help(pw) + } else if (helpSubCmds.isNotEmpty()) { + helpSubCmds.forEach { it.help(pw) } + } else { + if (!success) { + parser.generateValidationErrorMessages().forEach { pw.println(it) } + } else { + execute(pw) + } + } + } + + /** + * Returns a list of all commands that asked for help. If non-empty, parsing will stop to print + * help. It is not guaranteed that delegates are fulfilled if help is requested + */ + private fun subCmdsRequestingHelp(): List<ParseableCommand> = + parser.subCommands.filter { it.cmd.help }.map { it.cmd } + + /** Override to do something when parsing fails */ + open fun onParseFailed(error: ArgParseError) {} + + /** Override to print a usage clause. E.g. `usage: my-cmd <arg1> <arg2>` */ + open fun usage(pw: IndentingPrintWriter) {} + + /** + * Print out the list of tokens, their received types if any, and their description in a + * formatted string. + * + * Example: + * ``` + * my-command: + * MyCmd.description + * + * [optional] usage block + * + * Flags: + * -f + * description + * --flag2 + * description + * + * Parameters: + * Required: + * -p1 [Param.Type] + * description + * --param2 [Param.Type] + * description + * Optional: + * same as above + * + * SubCommands: + * Required: + * ... + * Optional: + * ... + * ``` + */ + override fun help(pw: PrintWriter) { + val ipw = IndentingPrintWriter(pw) + ipw.printBoxed(name) + ipw.println() + + // Allow for a simple `usage` block for clients + ipw.indented { usage(ipw) } + + if (description != null) { + ipw.indented { ipw.println(description) } + ipw.println() + } + + val flags = parser.flags + if (flags.isNotEmpty()) { + ipw.println("FLAGS:") + ipw.indented { + flags.forEach { + it.describe(ipw) + ipw.println() + } + } + } + + val (required, optional) = parser.params.partition { it is SingleArgParam<*> } + if (required.isNotEmpty()) { + ipw.println("REQUIRED PARAMS:") + required.describe(ipw) + } + if (optional.isNotEmpty()) { + ipw.println("OPTIONAL PARAMS:") + optional.describe(ipw) + } + + val (reqSub, optSub) = parser.subCommands.partition { it is RequiredSubCommand<*> } + if (reqSub.isNotEmpty()) { + ipw.println("REQUIRED SUBCOMMANDS:") + reqSub.describe(ipw) + } + if (optSub.isNotEmpty()) { + ipw.println("OPTIONAL SUBCOMMANDS:") + optSub.describe(ipw) + } + } + + fun flag( + longName: String, + shortName: String? = null, + description: String = "", + ): Flag { + if (!checkShortName(shortName)) { + throw IllegalArgumentException( + "Flag short name must be one character long, or null. Got ($shortName)" + ) + } + + if (!checkLongName(longName)) { + throw IllegalArgumentException("Flags must not start with '-'. Got $($longName)") + } + + val short = shortName?.let { "-$shortName" } + val long = "--$longName" + + return parser.flag(long, short, description) + } + + fun <T : Any> param( + longName: String, + shortName: String? = null, + description: String = "", + valueParser: ValueParser<T>, + ): SingleArgParamOptional<T> { + if (!checkShortName(shortName)) { + throw IllegalArgumentException( + "Parameter short name must be one character long, or null. Got ($shortName)" + ) + } + + if (!checkLongName(longName)) { + throw IllegalArgumentException("Parameters must not start with '-'. Got $($longName)") + } + + val short = shortName?.let { "-$shortName" } + val long = "--$longName" + + return parser.param(long, short, description, valueParser) + } + + fun <T : ParseableCommand> subCommand( + command: T, + ) = parser.subCommand(command) + + /** For use in conjunction with [param], makes the parameter required */ + fun <T : Any> SingleArgParamOptional<T>.required(): SingleArgParam<T> = parser.require(this) + + /** For use in conjunction with [subCommand], makes the given [SubCommand] required */ + fun <T : ParseableCommand> OptionalSubCommand<T>.required(): RequiredSubCommand<T> = + parser.require(this) + + private fun checkShortName(short: String?): Boolean { + return short == null || short.length == 1 + } + + private fun checkLongName(long: String): Boolean { + return !long.startsWith("-") + } + + companion object { + fun Iterable<Describable>.describe(pw: IndentingPrintWriter) { + pw.indented { + forEach { + it.describe(pw) + pw.println() + } + } + } + } +} + +/** + * A flag is a boolean value passed over the command line. It can have a short form or long form. + * The value is [Boolean.true] if the flag is found, else false + */ +data class Flag( + override val shortName: String? = null, + override val longName: String, + override val description: String? = null, +) : ReadOnlyProperty<Any?, Boolean>, Describable { + var inner: Boolean = false + + override fun getValue(thisRef: Any?, property: KProperty<*>) = inner +} + +/** + * Named CLI token. Can have a short or long name. Note: consider renaming to "primary" and + * "secondary" names since we don't actually care what the strings are + * + * Flags and params will have [shortName]s that are always prefixed with a single dash, while + * [longName]s are prefixed by a double dash. E.g., `my_command -f --flag`. + * + * Subcommands do not do any prefixing, and register their name as the [longName] + * + * Can be matched against an incoming token + */ +interface CliNamed { + val shortName: String? + val longName: String + + fun matches(token: String) = shortName == token || longName == token +} + +interface Describable : CliNamed { + val description: String? + + fun describe(pw: IndentingPrintWriter) { + if (shortName != null) { + pw.print("$shortName, ") + } + pw.print(longName) + pw.println() + if (description != null) { + pw.indented { pw.println(description) } + } + } +} + +/** + * Print [s] inside of a unicode character box, like so: + * ``` + * ╔═══════════╗ + * ║ my-string ║ + * ╚═══════════╝ + * ``` + */ +fun PrintWriter.printDoubleBoxed(s: String) { + val length = s.length + println("╔${"═".repeat(length + 2)}╗") + println("║ $s ║") + println("╚${"═".repeat(length + 2)}╝") +} + +/** + * Print [s] inside of a unicode character box, like so: + * ``` + * ┌───────────┐ + * │ my-string │ + * └───────────┘ + * ``` + */ +fun PrintWriter.printBoxed(s: String) { + val length = s.length + println("┌${"─".repeat(length + 2)}┐") + println("│ $s │") + println("└${"─".repeat(length + 2)}┘") +} + +fun IndentingPrintWriter.indented(block: () -> Unit) { + increaseIndent() + block() + decreaseIndent() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt new file mode 100644 index 000000000000..41bac86fd6c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import android.util.IndentingPrintWriter +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Sub commands wrap [ParseableCommand]s and are attached to a parent [ParseableCommand]. As such + * they have their own parser which will parse the args as a subcommand. I.e., the subcommand's + * parser will consume the iterator created by the parent, reversing the index when it reaches an + * unknown token. + * + * In order to keep subcommands relatively simple and not have to do complicated validation, sub + * commands will return control to the parent parser as soon as they discover a token that they do + * not own. They will throw an [ArgParseError] if parsing fails or if they don't receive arguments + * for a required parameter. + */ +sealed interface SubCommand : Describable { + val cmd: ParseableCommand + + /** Checks if all of the required elements were passed in to [parseSubCommandArgs] */ + var validationStatus: Boolean + + /** + * To keep parsing simple, [parseSubCommandArgs] requires a [ListIterator] so that it can rewind + * the iterator when it yields control upwards + */ + fun parseSubCommandArgs(iterator: ListIterator<String>) +} + +/** + * Note that the delegated type from the subcommand is `T: ParseableCommand?`. SubCommands are + * created via adding a fully-formed [ParseableCommand] to parent command. + * + * At this point in time, I don't recommend nesting subcommands. + */ +class OptionalSubCommand<T : ParseableCommand>( + override val cmd: T, +) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand?> { + override val shortName: String? = null + override val longName: String = cmd.name + override val description: String? = cmd.description + override var validationStatus = true + + private var isPresent = false + + /** Consume tokens from the iterator and pass them to the wrapped command */ + override fun parseSubCommandArgs(iterator: ListIterator<String>) { + validationStatus = cmd.parser.parseAsSubCommand(iterator) + isPresent = true + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = + if (isPresent) { + cmd + } else { + null + } + + override fun describe(pw: IndentingPrintWriter) { + cmd.help(pw) + } +} + +/** + * Non-optional subcommand impl. Top-level parser is expected to throw [ArgParseError] if this token + * is not present in the incoming command + */ +class RequiredSubCommand<T : ParseableCommand>( + override val cmd: T, +) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand> { + override val shortName: String? = null + override val longName: String = cmd.name + override val description: String? = cmd.description + override var validationStatus = true + + /** Unhandled, required subcommands are an error */ + var handled = false + + override fun parseSubCommandArgs(iterator: ListIterator<String>) { + validationStatus = cmd.parser.parseAsSubCommand(iterator) + handled = true + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): ParseableCommand = cmd + + override fun describe(pw: IndentingPrintWriter) { + cmd.help(pw) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt new file mode 100644 index 000000000000..01083d9a7907 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Utilities for parsing the [String] command line arguments. Arguments are related to the + * [Parameter] type, which declares the number of, and resulting type of, the arguments that it + * takes when parsing. For Example: + * ``` + * my-command --param <str> --param2 <int> + * ``` + * + * Defines 2 parameters, the first of which takes a string, and the second requires an int. Because + * fundamentally _everything_ is a string, we have to define a convenient way to get from the + * incoming `StringArg` to the resulting `T`-arg, where `T` is the type required by the client. + * + * Parsing is therefore a relatively straightforward operation: (String) -> T. However, since + * parsing can always fail, the type is actually (String) -> Result<T>. We will always want to fail + * on the first error and propagate it to the caller (typically this results in printing the `help` + * message of the command`). + * + * The identity parsing is trivial: + * ``` + * (s: String) -> String = { s -> s } + * ``` + * + * Basic mappings are actually even provided by Kotlin's stdlib: + * ``` + * (s: String) -> Boolean = { s -> s.toBooleanOrNull() } + * (s: String) -> Int = { s -> s.toIntOrNull() } + * ... + * ``` + * + * In order to properly encode errors, we will ascribe an error type to any `null` values, such that + * parsing looks like this: + * ``` + * val mapping: (String) -> T? = {...} // for some T + * val parser: (String) -> Result<T> = { s -> + * mapping(s)?.let { + * Result.success(it) + * } ?: Result.failure(/* some failure type */) + * } + * ``` + * + * Composition + * + * The ability to compose value parsing enables us to provide a couple of reasonable default parsers + * and allow clients to seamlessly build upon that using map functions. Consider the case where we + * want to validate that a value is an [Int] between 0 and 100. We start with the generic [Int] + * parser, and a validator, of the type (Int) -> Result<Int>: + * ``` + * val intParser = { s -> + * s.toStringOrNull().?let {...} ?: ... + * } + * + * val validator = { i -> + * if (i > 100 || i < 0) { + * Result.failure(...) + * } else { + * Result.success(i) + * } + * ``` + * + * In order to combine these functions, we need to define a new [flatMap] function that can get us + * from a `Result<T>` to a `Result<R>`, and short-circuit on any error. We want to see this: + * ``` + * val validatingParser = { s -> + * intParser.invoke(s).flatMap { i -> + * validator(i) + * } + * } + * ``` + * + * The flatMap is relatively simply defined, we can mimic the existing definition for [Result.map], + * though the implementation is uglier because of the `internal` definition for `value` + * + * ``` + * inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> { + * return when { + * isSuccess -> transform(getOrThrow()) + * else -> Result.failure(exceptionOrNull()!!) + * } + * } + * ``` + */ + +/** + * Given a [transform] that returns a [Result], apply the transform to this result, unwrapping the + * return value so that + * + * These [contract] and [callsInPlace] methods are copied from the [Result.map] definition + */ +@OptIn(ExperimentalContracts::class) +inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> { + contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } + + return when { + // Should never throw, we just don't have access to [this.value] + isSuccess -> transform(getOrThrow()) + // Exception should never be null here + else -> Result.failure(exceptionOrNull()!!) + } +} + +/** + * ValueParser turns a [String] into a Result<A> by applying a transform. See the default + * implementations below for starting points. The intention here is to provide the base mappings and + * allow clients to attach their own transforms. They are expected to succeed or return null on + * failure. The failure is propagated to the command parser as a Result and will fail on any + * [Result.failure] + */ +fun interface ValueParser<out A> { + fun parseValue(value: String): Result<A> +} + +/** Map a [ValueParser] of type A to one of type B, by applying the given [transform] */ +inline fun <A, B> ValueParser<A>.map(crossinline transform: (A) -> B?): ValueParser<B> { + return ValueParser<B> { value -> + this.parseValue(value).flatMap { a -> + transform(a)?.let { b -> Result.success(b) } + ?: Result.failure(ArgParseError("Failed to transform value $value")) + } + } +} + +/** + * Base type parsers are provided by the lib, and can be simply composed upon by [ValueParser.map] + * functions on the parser + */ + +/** String parsing always succeeds if the value exists */ +private val parseString: ValueParser<String> = ValueParser { value -> Result.success(value) } + +private val parseBoolean: ValueParser<Boolean> = ValueParser { value -> + value.toBooleanStrictOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as a boolean")) +} + +private val parseInt: ValueParser<Int> = ValueParser { value -> + value.toIntOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as an int")) +} + +private val parseFloat: ValueParser<Float> = ValueParser { value -> + value.toFloatOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as a float")) +} + +/** Default parsers that can be use as-is, or [map]ped to another type */ +object Type { + val Boolean = parseBoolean + val Int = parseInt + val Float = parseFloat + val String = parseString +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 075b41b91d97..035fa0454bfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -41,6 +41,8 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; @@ -273,6 +275,21 @@ public interface CentralSurfacesDependenciesModule { return ongoingCallController; } + /** + * {@link NotificationPanelViewController} implements two interfaces: + * - {@link com.android.systemui.shade.ShadeViewController}, which can be used by any class + * needing access to the shade. + * - {@link ShadeSurface}, which should *only* be used by {@link CentralSurfacesImpl}. + * + * Since {@link ShadeSurface} should only be accessible by {@link CentralSurfacesImpl}, it's + * *only* bound in this CentralSurfaces dependencies module. + * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in + * {@link com.android.systemui.shade.ShadeModule} so others can access it. + */ + @Binds + @SysUISingleton + ShadeSurface provideShadeSurface(NotificationPanelViewController impl); + /** */ @Binds ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 877846336562..11b1053f35b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -124,6 +124,7 @@ constructor( private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null + private var mSplitShadeEnabled = false // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to // how we test color updates when theme changes (See testThemeChangeUpdatesTextColor). @@ -131,6 +132,7 @@ constructor( // TODO: Move logic into SmartspaceView var stateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { + (v as SmartspaceView).setSplitShadeEnabled(mSplitShadeEnabled) smartspaceViews.add(v as SmartspaceView) connectSession() @@ -221,6 +223,11 @@ constructor( execution.assertIsMainThread() smartspaceViews.forEach { it.setDozeAmount(eased) } } + + override fun onDozingChanged(isDozing: Boolean) { + execution.assertIsMainThread() + smartspaceViews.forEach { it.setDozing(isDozing) } + } } private val deviceProvisionedListener = @@ -426,6 +433,11 @@ constructor( reloadSmartspace() } + fun setSplitShadeEnabled(enabled: Boolean) { + mSplitShadeEnabled = enabled + smartspaceViews.forEach { it.setSplitShadeEnabled(enabled) } + } + /** * Requests the smartspace session for an update. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 8af488ea443d..27510d47b5ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -359,9 +359,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override - public long performRemoveAnimation(long duration, long delay, - float translationDirection, boolean isHeadsUpAnimation, float endLocation, - Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { + public long performRemoveAnimation(long duration, long delay, float translationDirection, + boolean isHeadsUpAnimation, Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; if (mDrawingAppearAnimation) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 30747db0fb64..b34c28163abb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2975,7 +2975,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, - float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { @@ -2986,7 +2985,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - endLocation, onFinishedRunnable, animationListener); + onFinishedRunnable, animationListener); } }); anim.start(); @@ -2994,7 +2993,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onFinishedRunnable, animationListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index f0e15c27b7a7..f98624409e56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -367,7 +367,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro * such that the child appears to be going away to the top. 1 * Should mean the opposite. * @param isHeadsUpAnimation Is this a headsUp animation. - * @param endLocation The location where the horizonal heads up disappear animation should end. * @param onFinishedRunnable A runnable which should be run when the animation is finished. * @param animationListener An animation listener to add to the animation. * @@ -375,7 +374,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro * animation starts. */ public abstract long performRemoveAnimation(long duration, - long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, + long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index b24cec150941..0c686be0406d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -235,7 +235,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { @Override public long performRemoveAnimation(long duration, long delay, - float translationDirection, boolean isHeadsUpAnimation, float endLocation, + float translationDirection, boolean isHeadsUpAnimation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index b8f28b5a60ea..04308b47abc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -69,11 +69,14 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie canvas.clipPath(clipPath) } - - override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float, - isHeadsUpAnimation: Boolean, endLocation: Float, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter?): Long { + override fun performRemoveAnimation( + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter? + ): Long { return 0 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 7c66bbe548a5..ef7375aa690b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -424,7 +424,8 @@ public class NotificationStackScrollLayoutController { } }; - private final NotificationSwipeHelper.NotificationCallback mNotificationCallback = + @VisibleForTesting + final NotificationSwipeHelper.NotificationCallback mNotificationCallback = new NotificationSwipeHelper.NotificationCallback() { @Override @@ -483,10 +484,11 @@ public class NotificationStackScrollLayoutController { */ public void handleChildViewDismissed(View view) { + // The View needs to clean up the Swipe states, e.g. roundness. + mView.onSwipeEnd(); if (mView.getClearAllInProgress()) { return; } - mView.onSwipeEnd(); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (row.isHeadsUp()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index d73919b82c42..2742a23d5fad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -27,8 +27,6 @@ import com.android.keyguard.KeyguardSliceView; import com.android.systemui.R; import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.NotificationShelf; -import com.android.systemui.statusbar.StatusBarIconView; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; @@ -427,7 +425,7 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - 0, postAnimation, null); + postAnimation, null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { if (mHostLayout.isFullySwipedOut(changingView)) { @@ -474,28 +472,12 @@ public class StackStateAnimator { mTmpState.initFrom(changingView); endRunnable = changingView::removeFromTransientContainer; } - float targetLocation = 0; boolean needsAnimation = true; if (changingView instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; if (row.isDismissed()) { needsAnimation = false; } - - NotificationEntry entry = row.getEntry(); - StatusBarIconView icon = entry.getIcons().getStatusBarIcon(); - final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon(); - if (centeredIcon != null && centeredIcon.getParent() != null) { - icon = centeredIcon; - } - if (icon.getParent() != null) { - icon.getLocationOnScreen(mTmpLocation); - float iconPosition = mTmpLocation[0] - icon.getTranslationX() - + ViewState.getFinalTranslationX(icon) - + icon.getWidth() * 0.25f; - mHostLayout.getLocationOnScreen(mTmpLocation); - targetLocation = iconPosition - mTmpLocation[0]; - } } if (needsAnimation) { @@ -515,7 +497,7 @@ public class StackStateAnimator { } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, - 0, 0.0f, true /* isHeadsUpAppear */, targetLocation, + 0, 0.0f, true /* isHeadsUpAppear */, postAnimation, getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 8b5db28c2bd1..0d3dfaeb85b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -182,7 +182,6 @@ import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; @@ -477,14 +476,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy; /** Controller for the Shade. */ - @VisibleForTesting - ShadeSurface mShadeSurface; + private final ShadeSurface mShadeSurface; private final ShadeLogger mShadeLogger; // settings private QSPanelController mQSPanelController; - @VisibleForTesting - QuickSettingsController mQsController; + private final QuickSettingsController mQsController; KeyguardIndicationController mKeyguardIndicationController; @@ -704,9 +701,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { MetricsLogger metricsLogger, ShadeLogger shadeLogger, @UiBackground Executor uiBgExecutor, + ShadeSurface shadeSurface, NotificationMediaManager notificationMediaManager, NotificationLockscreenUserManager lockScreenUserManager, NotificationRemoteInputManager remoteInputManager, + QuickSettingsController quickSettingsController, UserSwitcherController userSwitcherController, BatteryController batteryController, SysuiColorExtractor colorExtractor, @@ -805,9 +804,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mMetricsLogger = metricsLogger; mShadeLogger = shadeLogger; mUiBgExecutor = uiBgExecutor; + mShadeSurface = shadeSurface; mMediaManager = notificationMediaManager; mLockscreenUserManager = lockScreenUserManager; mRemoteInputManager = remoteInputManager; + mQsController = quickSettingsController; mUserSwitcherController = userSwitcherController; mBatteryController = batteryController; mColorExtractor = colorExtractor; @@ -1611,13 +1612,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // (Right now, there's a circular dependency.) mNotificationShadeWindowController.setWindowRootView(windowRootView); mNotificationShadeWindowViewController.setupExpandedStatusBar(); - NotificationPanelViewController npvc = - mCentralSurfacesComponent.getNotificationPanelViewController(); - mShadeSurface = npvc; - mShadeController.setNotificationPanelViewController(npvc); + mShadeController.setShadeViewController(mShadeSurface); mShadeController.setNotificationShadeWindowViewController( mNotificationShadeWindowViewController); - mQsController = mCentralSurfacesComponent.getQuickSettingsController(); mBackActionInteractor.setup(mQsController, mShadeSurface); mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter(); @@ -1813,7 +1810,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onStatusBarTrackpadEvent(MotionEvent event) { - mCentralSurfacesComponent.getNotificationPanelViewController().handleExternalTouch(event); + mShadeSurface.handleExternalTouch(event); } private void onExpandedInvisible() { @@ -2180,9 +2177,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { */ @Override public void setLockscreenUser(int newUserId) { - if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - mLockscreenWallpaper.setCurrentUser(newUserId); - } if (mWallpaperSupported) { mWallpaperChangedReceiver.onReceive(mContext, null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index c07b5e062d70..b2c39f7e289f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -40,12 +40,17 @@ import androidx.annotation.NonNull; import com.android.internal.util.IndentingPrintWriter; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.user.data.model.SelectedUserModel; +import com.android.systemui.user.data.model.SelectionStatus; +import com.android.systemui.user.data.repository.UserRepository; +import com.android.systemui.util.kotlin.JavaAdapter; import libcore.io.IoUtils; @@ -59,7 +64,7 @@ import javax.inject.Inject; */ @SysUISingleton public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable, - Dumpable { + Dumpable, CoreStartable { private static final String TAG = "LockscreenWallpaper"; @@ -72,6 +77,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen private final WallpaperManager mWallpaperManager; private final KeyguardUpdateMonitor mUpdateMonitor; private final Handler mH; + private final JavaAdapter mJavaAdapter; + private final UserRepository mUserRepository; private boolean mCached; private Bitmap mCache; @@ -88,6 +95,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen DumpManager dumpManager, NotificationMediaManager mediaManager, @Main Handler mainHandler, + JavaAdapter javaAdapter, + UserRepository userRepository, UserTracker userTracker) { dumpManager.registerDumpable(getClass().getSimpleName(), this); mWallpaperManager = wallpaperManager; @@ -95,6 +104,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen mUpdateMonitor = keyguardUpdateMonitor; mMediaManager = mediaManager; mH = mainHandler; + mJavaAdapter = javaAdapter; + mUserRepository = userRepository; if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { // Service is disabled on some devices like Automotive @@ -106,6 +117,14 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } + @Override + public void start() { + if (!isLockscreenLiveWallpaperEnabled()) { + mJavaAdapter.alwaysCollectFlow( + mUserRepository.getSelectedUser(), this::setSelectedUser); + } + } + public Bitmap getBitmap() { assertLockscreenLiveWallpaperNotEnabled(); @@ -169,9 +188,15 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } - public void setCurrentUser(int user) { + private void setSelectedUser(SelectedUserModel selectedUserModel) { assertLockscreenLiveWallpaperNotEnabled(); + if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) { + // Wait until the selection has finished before updating. + return; + } + + int user = selectedUserModel.getUserInfo().id; if (user != mCurrentUserId) { if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) { mCached = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index c618be843832..4ae460a3f0e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -21,10 +21,8 @@ import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.ST import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.scene.ui.view.WindowRootView; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; -import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -89,14 +87,6 @@ public interface CentralSurfacesComponent { NotificationShadeWindowViewController getNotificationShadeWindowViewController(); /** - * Creates a NotificationPanelViewController. - */ - NotificationPanelViewController getNotificationPanelViewController(); - - /** Creates a QuickSettingsController. */ - QuickSettingsController getQuickSettingsController(); - - /** * Creates a StatusBarHeadsUpChangeListener. */ StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 421bdc69cb24..ebdde78e4ea4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -21,18 +21,15 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; -import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; @@ -44,10 +41,8 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.util.settings.SecureSettings; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.multibindings.IntoSet; import java.util.concurrent.Executor; @@ -65,17 +60,6 @@ public abstract class StatusBarViewModule { public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment"; - /** */ - @Binds - @CentralSurfacesComponent.CentralSurfacesScope - abstract ShadeViewController bindsShadeViewController( - NotificationPanelViewController notificationPanelViewController); - - @Binds - @IntoSet - abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( - SystemBarAttributesListener systemBarAttributesListener); - /** * Creates a new {@link CollapsedStatusBarFragment}. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 730ecded58e2..97a1bd1851d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -26,20 +26,23 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; +import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; +import dagger.multibindings.Multibinds; + import java.util.Optional; import java.util.Set; import javax.inject.Named; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.Multibinds; - /** Dagger module for {@link StatusBarFragmentComponent}. */ @Module public interface StatusBarFragmentModule { @@ -151,4 +154,10 @@ public interface StatusBarFragmentModule { /** */ @Multibinds Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners(); + + /** */ + @Binds + @IntoSet + StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( + SystemBarAttributesListener systemBarAttributesListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index a4fbc2c93647..a57be665f105 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -96,7 +96,7 @@ constructor( networkId = DEMO_NET_ID, isValidated = validated ?: true, level = level ?: 0, - ssid = ssid, + ssid = ssid ?: DEMO_NET_SSID, // These fields below aren't supported in demo mode, since they aren't needed to satisfy // the interface. @@ -115,5 +115,6 @@ constructor( companion object { private const val DEMO_NET_ID = 1234 + private const val DEMO_NET_SSID = "Demo SSID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index b135d0d8c9dc..1c3a8850df8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -28,6 +28,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings.Global; @@ -122,7 +123,12 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { - updateZenModeConfig(); + try { + Trace.beginSection("updateZenModeConfig"); + updateZenModeConfig(); + } finally { + Trace.endSection(); + } } }; mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index bcf3b0cbfc86..4a9921eb2d83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -240,8 +240,10 @@ public class StatusBarWindowController { Insets.of(0, safeTouchRegionHeight, 0, 0)); } lp.providedInsets = new InsetsFrameProvider[] { - new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()), - new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()), + new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()) + .setInsetsSize(Insets.of(0, height, 0, 0)), + new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()) + .setInsetsSize(Insets.of(0, height, 0, 0)), gestureInsetsProvider }; return lp; diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt new file mode 100644 index 000000000000..cefd466374c0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.user.data.model + +import android.content.pm.UserInfo + +/** A model for the currently selected user. */ +data class SelectedUserModel( + /** Information about the user. */ + val userInfo: UserInfo, + /** The current status of the selection. */ + val selectionStatus: SelectionStatus, +) + +/** The current status of the selection. */ +enum class SelectionStatus { + /** This user has started being selected but the selection hasn't completed. */ + SELECTION_IN_PROGRESS, + /** The selection of this user has completed. */ + SELECTION_COMPLETE, +} diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 3de75ca2ed87..954765c4581d 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -33,6 +33,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -69,6 +71,9 @@ interface UserRepository { /** List of all users on the device. */ val userInfos: Flow<List<UserInfo>> + /** Information about the currently-selected user, including [UserInfo] and other details. */ + val selectedUser: StateFlow<SelectedUserModel> + /** [UserInfo] of the currently-selected user. */ val selectedUserInfo: Flow<UserInfo> @@ -146,9 +151,6 @@ constructor( private val _userInfos = MutableStateFlow<List<UserInfo>?>(null) override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() - override var mainUserId: Int = UserHandle.USER_NULL private set override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL @@ -174,12 +176,57 @@ constructor( override var isRefreshUsersPaused: Boolean = false init { - observeSelectedUser() if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) { observeUserSwitching() } } + override val selectedUser: StateFlow<SelectedUserModel> = run { + // Some callbacks don't modify the selection status, so maintain the current value. + var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE + conflatedCallbackFlow { + fun send(selectionStatus: SelectionStatus) { + currentSelectionStatus = selectionStatus + trySendWithFailureLogging( + SelectedUserModel(tracker.userInfo, selectionStatus), + TAG, + ) + } + + val callback = + object : UserTracker.Callback { + override fun onUserChanging(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_IN_PROGRESS) + } + + override fun onUserChanged(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_COMPLETE) + } + + override fun onProfilesChanged(profiles: List<UserInfo>) { + send(currentSelectionStatus) + } + } + + tracker.addCallback(callback, mainDispatcher.asExecutor()) + send(currentSelectionStatus) + + awaitClose { tracker.removeCallback(callback) } + } + .onEach { + if (!it.userInfo.isGuest) { + lastSelectedNonGuestUserId = it.userInfo.id + } + } + .stateIn( + applicationScope, + SharingStarted.Eagerly, + initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus) + ) + } + + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } + override fun refreshUsers() { applicationScope.launch { val result = withContext(backgroundDispatcher) { manager.aliveUsers } @@ -201,7 +248,7 @@ constructor( } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -234,38 +281,6 @@ constructor( .launchIn(applicationScope) } - private fun observeSelectedUser() { - conflatedCallbackFlow { - fun send() { - trySendWithFailureLogging(tracker.userInfo, TAG) - } - - val callback = - object : UserTracker.Callback { - override fun onUserChanging(newUser: Int, userContext: Context) { - send() - } - - override fun onProfilesChanged(profiles: List<UserInfo>) { - send() - } - } - - tracker.addCallback(callback, mainDispatcher.asExecutor()) - send() - - awaitClose { tracker.removeCallback(callback) } - } - .onEach { - if (!it.isGuest) { - lastSelectedNonGuestUserId = it.id - } - - _selectedUserInfo.value = it - } - .launchIn(applicationScope) - } - private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 7456d349a933..93622200ad46 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -476,7 +476,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 0dcd404d2fc5..231898884adf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -21,21 +21,22 @@ import android.view.View import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.flags.FeatureFlags -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockEvents -import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceConfig +import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceEvents import com.android.systemui.plugins.ClockTickRate -import com.android.systemui.log.LogBuffer import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController @@ -64,7 +65,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import java.util.TimeZone import java.util.concurrent.Executor -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @@ -122,7 +122,9 @@ class ClockEventControllerTest : SysuiTestCase() { bouncerRepository = bouncerRepository, configurationRepository = FakeConfigurationRepository(), ), - KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, broadcastDispatcher, batteryController, keyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index b21cc6dde815..9e561ed290f7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -408,4 +408,18 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { any(ClockRegistry.ClockChangeListener.class)); verify(mClockEventController, times).registerListeners(mView); } + + @Test + public void testSplitShadeEnabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(true); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void testSplitShadeDisabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(false); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 5a56bafc1992..d3b41902499c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -97,7 +97,21 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) .thenReturn(deleteButton) `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) - constructViewController() + pinViewController = + KeyguardPinViewController( + keyguardPinView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + mKeyguardSecurityCallback, + keyguardMessageAreaControllerFactory, + mLatencyTracker, + liftToActivateListener, + mEmergencyButtonController, + falsingCollector, + postureController, + featureFlags + ) } @Test @@ -121,10 +135,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3) `when`(passwordTextView.text).thenReturn("") - constructViewController() pinViewController.startAppearAnimation() - verify(deleteButton).visibility = View.INVISIBLE verify(enterButton).visibility = View.INVISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -138,10 +150,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6) `when`(passwordTextView.text).thenReturn("") - constructViewController() pinViewController.startAppearAnimation() - verify(deleteButton).visibility = View.VISIBLE verify(enterButton).visibility = View.VISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -153,22 +163,4 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { pinViewController.handleAttemptLockout(0) verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt()) } - - fun constructViewController() { - pinViewController = - KeyguardPinViewController( - keyguardPinView, - keyguardUpdateMonitor, - securityMode, - lockPatternUtils, - mKeyguardSecurityCallback, - keyguardMessageAreaControllerFactory, - mLatencyTracker, - liftToActivateListener, - mEmergencyButtonController, - falsingCollector, - postureController, - featureFlags - ) - } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index e561f1f233b4..58b1edcc5511 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -62,12 +62,12 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsUiRequestSource; +import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; @@ -395,6 +395,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { // WHEN a request is made from the SimPin screens to show the next security method when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); + when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true); mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( /* authenticated= */true, TARGET_USER_ID, @@ -423,6 +424,28 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test + public void showNextSecurityScreenOrFinish_SimPin_Swipe() { + // GIVEN the current security method is SimPin + when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false); + mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin); + + // WHEN a request is made from the SimPin screens to show the next security method + when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); + // WHEN security method is SWIPE + when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false); + mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( + /* authenticated= */true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */true, + SecurityMode.SimPin); + + // THEN the next security method of None will dismiss keyguard. + verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); + } + + + @Test public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { KeyguardSecurityContainer.SwipeListener registeredSwipeListener = getRegisteredSwipeListener(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index a2c632936047..512e5dc1a0d6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -155,4 +156,18 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true); verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true); } + + @Test + public void splitShadeEnabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void splitShadeDisabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index c88c4d65f412..75106e75c1ab 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; +import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -41,6 +42,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.doze.util.BurnInHelperKt; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; @@ -92,6 +94,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected @Mock KeyguardTransitionRepository mTransitionRepository; protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); protected FakeFeatureFlags mFeatureFlags; + protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; protected LockIconViewController mUnderTest; @@ -143,6 +146,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(FACE_AUTH_REFACTOR, false); + mFeatureFlags.set(MIGRATE_LOCK_ICON, false); mUnderTest = new LockIconViewController( mLockIconView, mStatusBarStateController, @@ -161,7 +165,8 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { new KeyguardTransitionInteractor(mTransitionRepository, TestScopeProvider.getTestScope().getBackgroundScope()), KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(), - mFeatureFlags + mFeatureFlags, + mPrimaryBouncerInteractor ); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index b62875988b2e..ed6a891a6094 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -33,6 +33,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; +import android.view.View; import androidx.test.filters.SmallTest; @@ -267,4 +268,75 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN the lock icon is shown verify(mLockIconView).setContentDescription(LOCKED_LABEL); } + + @Test + public void lockIconAccessibility_notVisibleToUser() { + // GIVEN lock icon controller is initialized and view is attached + init(/* useMigrationFlag= */false); + captureKeyguardStateCallback(); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + // and biometric running state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + reset(mLockIconView); + when(mLockIconView.isVisibleToUser()).thenReturn(false); + + // WHEN the unlocked state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); + mKeyguardStateCallback.onUnlockedChanged(); + + // THEN the lock icon is shown + verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + @Test + public void lockIconAccessibility_bouncerAnimatingAway() { + // GIVEN lock icon controller is initialized and view is attached + init(/* useMigrationFlag= */false); + captureKeyguardStateCallback(); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + // and biometric running state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + reset(mLockIconView); + when(mLockIconView.isVisibleToUser()).thenReturn(true); + when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true); + + // WHEN the unlocked state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); + mKeyguardStateCallback.onUnlockedChanged(); + + // THEN the lock icon is shown + verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + @Test + public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() { + // GIVEN lock icon controller is initialized and view is attached + init(/* useMigrationFlag= */false); + captureKeyguardStateCallback(); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + // and biometric running state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + reset(mLockIconView); + when(mLockIconView.isVisibleToUser()).thenReturn(true); + when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false); + + // WHEN the unlocked state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); + mKeyguardStateCallback.onUnlockedChanged(); + + // THEN the lock icon is shown + verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 79c87cfd1f3e..796e66514afe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -96,6 +96,7 @@ import com.android.systemui.log.ScreenDecorationsLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; @@ -139,6 +140,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private Display mDisplay; @Mock + private CommandRegistry mCommandRegistry; + @Mock private UserTracker mUserTracker; @Mock private PrivacyDotViewController mDotViewController; @@ -231,8 +234,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { mExecutor, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")))); - mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, - mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, + mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings, + mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController, + mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController) { @@ -1226,8 +1230,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { mFaceScanningProviders.add(mFaceScanningDecorProvider); when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders); when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true); - ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor, - mSecureSettings, mUserTracker, mDisplayTracker, mDotViewController, + ScreenDecorations screenDecorations = new ScreenDecorations(mContext, + mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker, + mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController); screenDecorations.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java index f64db78f4941..caf230d4e793 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java @@ -16,8 +16,10 @@ package com.android.systemui.accessibility; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; @@ -185,6 +187,19 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { verify(mMagnificationSettingsController).closeMagnificationSettings(); } + @Test + public void onUserMagnificationScaleChanged() throws RemoteException { + final int testUserId = 1; + final float testScale = 3.0f; + mIWindowMagnificationConnection.onUserMagnificationScaleChanged( + testUserId, TEST_DISPLAY, testScale); + waitForIdleSync(); + + assertTrue(mWindowMagnification.mUsersScales.contains(testUserId)); + assertEquals(mWindowMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY), + (Float) testScale); + } + private class FakeControllerSupplier extends DisplayIdIndexSupplier<WindowMagnificationController> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt index a10f5dd4878d..d5e6881500bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView +import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture @@ -67,7 +68,7 @@ class FontScalingDialogTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Captor - private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener> + private lateinit var seekBarChangeCaptor: ArgumentCaptor<OnSeekBarWithIconButtonsChangeListener> @Before fun setUp() { @@ -176,16 +177,17 @@ class FontScalingDialogTest : SysuiTestCase() { fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() { fontScalingDialog.show() - val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = - fontScalingDialog.findViewById(R.id.font_scaling_slider)!! + val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!! secureSettings.putIntForUser( Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF, userTracker.userId ) - // Default seekbar progress for font size is 1, set it to another progress 0 - seekBarWithIconButtonsView.setProgress(0) + // Default seekbar progress for font size is 1, click start icon to decrease the progress + iconStartFrame.performClick() + backgroundDelayableExecutor.runAllReady() + backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() val currentSettings = @@ -208,7 +210,7 @@ class FontScalingDialogTest : SysuiTestCase() { ) .thenReturn(slider) fontScalingDialog.show() - verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor)) + verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor)) val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without @@ -237,6 +239,15 @@ class FontScalingDialogTest : SysuiTestCase() { backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() + // SeekBar interaction is finalized. + seekBarChangeCaptor.value.onUserInteractionFinalized( + seekBar, + OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER + ) + backgroundDelayableExecutor.runAllReady() + backgroundDelayableExecutor.advanceClockToNext() + backgroundDelayableExecutor.runAllReady() + // Verify that the scale of font size has been updated. systemScale = systemSettings.getFloatForUser( @@ -258,7 +269,7 @@ class FontScalingDialogTest : SysuiTestCase() { ) .thenReturn(slider) fontScalingDialog.show() - verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor)) + verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor)) val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt new file mode 100644 index 000000000000..7de78a60b73e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class UdfpsBpViewControllerTest : SysuiTestCase() { + + @JvmField @Rule var rule = MockitoJUnit.rule() + + @Mock lateinit var udfpsBpView: UdfpsBpView + @Mock lateinit var statusBarStateController: StatusBarStateController + @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock lateinit var systemUIDialogManager: SystemUIDialogManager + @Mock lateinit var dumpManager: DumpManager + + private lateinit var udfpsBpViewController: UdfpsBpViewController + + @Before + fun setup() { + udfpsBpViewController = + UdfpsBpViewController( + udfpsBpView, + statusBarStateController, + shadeExpansionStateManager, + systemUIDialogManager, + dumpManager + ) + } + + @Test + fun testShouldNeverPauseAuth() { + assertFalse(udfpsBpViewController.shouldPauseAuth()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 224875590d75..0e0d0e3f3748 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -43,11 +43,12 @@ import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -58,6 +59,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Rule import org.junit.Test @@ -70,6 +72,7 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import javax.inject.Provider import org.mockito.Mockito.`when` as whenever private const val REQUEST_ID = 2L @@ -80,6 +83,7 @@ private const val DISPLAY_HEIGHT = 1920 private const val SENSOR_WIDTH = 30 private const val SENSOR_HEIGHT = 60 +@ExperimentalCoroutinesApi @SmallTest @RoboPilotTest @RunWith(AndroidJUnit4::class) @@ -116,6 +120,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsUtils: UdfpsUtils @Mock private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate + @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels> @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } @@ -148,6 +153,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerCallback, onTouch, activityLaunchAnimator, featureFlags, primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils, udfpsKeyguardAccessibilityDelegate, + udfpsKeyguardViewModels, ) block() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 30e54474bbde..58982d13481d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; +import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -59,6 +60,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.VibrationAttributes; import android.testing.TestableLooper.RunWithLooper; +import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -83,13 +85,14 @@ import com.android.systemui.biometrics.udfps.InteractionEvent; import com.android.systemui.biometrics.udfps.NormalizedTouchData; import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; import com.android.systemui.biometrics.udfps.TouchProcessorResult; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -219,6 +222,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; + @Mock + private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; // Capture listeners so that they can be used to send events @Captor @@ -318,7 +323,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker, mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils, mock(KeyguardFaceAuthInteractor.class), - mUdfpsKeyguardAccessibilityDelegate); + mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -1200,8 +1205,53 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void fingerDown_falsingManagerInformed() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // THEN falsing manager is informed of the touch + verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); + } + + @Test public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_UP is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.second); + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the new FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + } + + private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent() + throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( @@ -1227,27 +1277,7 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUp); - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the new FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + return new Pair<>(processorResultDown, processorResultUp); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt index 263ce1a2e9f5..8f8004f1cbb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt @@ -7,7 +7,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -54,10 +54,11 @@ class LogContextInteractorImplTest : SysuiTestCase() { LogContextInteractorImpl( testScope.backgroundScope, foldProvider, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + repository = keyguardTransitionRepository, + scope = testScope.backgroundScope, + ) + .keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 22ac1b678dcc..f811ce05e314 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -42,6 +42,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { ) private val underTest = PinBouncerViewModel( + applicationContext = context, applicationScope = testScope.backgroundScope, interactor = utils.bouncerInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 61432e2df274..608a187a1bc9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -67,6 +67,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { ) private val underTest = PinBouncerViewModel( + applicationContext = context, applicationScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java index afd9be5787c9..f0006e5e0ba7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java @@ -18,6 +18,13 @@ package com.android.systemui.common.ui.view; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.ViewGroup; @@ -28,10 +35,15 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; /** * Tests for {@link SeekBarWithIconButtonsView} @@ -48,14 +60,22 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase { private SeekBar mSeekbar; private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout; + @Mock + private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener; + @Before public void setUp() { + MockitoAnnotations.initMocks(this); + mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext); mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start); mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end); mIconStartFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start_frame); mIconEndFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end_frame); mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar); + + mIconDiscreteSliderLinearLayout.setOnSeekBarWithIconButtonsChangeListener( + mOnSeekBarChangeListener); } @Test @@ -109,6 +129,49 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase { } @Test + public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() { + reset(mOnSeekBarChangeListener); + mIconDiscreteSliderLinearLayout.setProgress(1); + + verify(mOnSeekBarChangeListener).onProgressChanged( + eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false)); + verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any()); + verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any()); + verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized( + /* seekBar= */any(), /* control= */ anyInt()); + } + + @Test + public void clickIconEnd_triggerCallbacksInSequence() { + final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude(); + mIconDiscreteSliderLinearLayout.setProgress(0); + reset(mOnSeekBarChangeListener); + + mIconEndFrame.performClick(); + + InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener); + inOrder.verify(mOnSeekBarChangeListener).onProgressChanged( + eq(mSeekbar), /* progress= */ eq(magnitude), /* fromUser= */ eq(true)); + inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized( + eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON)); + } + + @Test + public void clickIconStart_triggerCallbacksInSequence() { + final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude(); + mIconDiscreteSliderLinearLayout.setProgress(magnitude); + reset(mOnSeekBarChangeListener); + + mIconStartFrame.performClick(); + + InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener); + inOrder.verify(mOnSeekBarChangeListener).onProgressChanged( + eq(mSeekbar), /* progress= */ eq(0), /* fromUser= */ eq(true)); + inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized( + eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON)); + } + + @Test public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() { String[] stateLabels = new String[]{"1", "2", "3", "4", "5"}; mIconDiscreteSliderLinearLayout.setMax(stateLabels.length); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 6f7c217e4f3a..666978e78f98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -205,6 +205,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot); when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha()) .thenReturn(mock(Flow.class)); + when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded()) + .thenReturn(mock(Flow.class)); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mViewMediator, mKeyguardBypassController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index b4bd473b8b8c..925ac30b99fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -10,7 +10,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -67,10 +67,11 @@ class ResourceTrimmerTest : SysuiTestCase() { resourceTrimmer = ResourceTrimmer( keyguardInteractor, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, globalWindowManager, testScope.backgroundScope, testDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index e042564646e7..d62db5d1b890 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -49,7 +49,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.AuthenticationStatus import com.android.systemui.keyguard.shared.model.DetectionStatus import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus @@ -216,7 +216,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) keyguardTransitionRepository = FakeKeyguardTransitionRepository() val keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor return DeviceEntryFaceAuthRepositoryImpl( mContext, fmOverride, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index ee5c1cc31b0b..3e81cd336824 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -89,7 +89,11 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { faceAuthRepository = FakeDeviceEntryFaceAuthRepository() keyguardTransitionRepository = FakeKeyguardTransitionRepository() keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = SystemUIKeyguardFaceAuthInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index e9c22f9d551d..0050d64d7505 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -292,10 +292,11 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, repository = keyguardRepository, logger = logger, featureFlags = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index fa4941cbb895..9e9c25eafa33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.systemui.RoboPilotTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -54,7 +54,10 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope) + underTest = KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ).keyguardTransitionInteractor } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index b5590154f7f4..d01a46e06b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN +import com.android.keyguard.TestScopeProvider import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.flags.FakeFeatureFlags @@ -92,76 +93,97 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() + testScope = TestScopeProvider.getTestScope() keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() shadeRepository = FakeShadeRepository() transitionRepository = spy(FakeKeyguardTransitionRepository()) - transitionInteractor = KeyguardTransitionInteractor( - transitionRepository, testScope.backgroundScope) whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } - fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - shadeRepository = shadeRepository, - ).apply { start() } - - fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - keyguardSecurityModel = keyguardSecurityModel, - ).apply { start() } - - fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAodTransitionInteractor = FromAodTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromGoneTransitionInteractor = FromGoneTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromDozingTransitionInteractor = FromDozingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } + transitionInteractor = + KeyguardTransitionInteractorFactory.create( + scope = testScope, + repository = transitionRepository, + ) + .keyguardTransitionInteractor + + fromLockscreenTransitionInteractor = + FromLockscreenTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + shadeRepository = shadeRepository, + ) + .apply { start() } + + fromPrimaryBouncerTransitionInteractor = + FromPrimaryBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + keyguardSecurityModel = keyguardSecurityModel, + ) + .apply { start() } + + fromDreamingTransitionInteractor = + FromDreamingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAodTransitionInteractor = + FromAodTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromGoneTransitionInteractor = + FromGoneTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromDozingTransitionInteractor = + FromDozingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromOccludedTransitionInteractor = + FromOccludedTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAlternateBouncerTransitionInteractor = + FromAlternateBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index 08e99dc6b7d0..6e7ba6dd1ecb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -46,7 +46,11 @@ class LightRevealScrimInteractorTest : SysuiTestCase() { private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository() private val keyguardTransitionInteractor = - KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = fakeKeyguardTransitionRepository, + ) + .keyguardTransitionInteractor private lateinit var underTest: LightRevealScrimInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt new file mode 100644 index 000000000000..1baca2184e9b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsKeyguardInteractorTest : SysuiTestCase() { + private val burnInProgress = 1f + private val burnInYOffset = 20 + private val burnInXOffset = 10 + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var burnInInteractor: BurnInInteractor + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + private lateinit var underTest: UdfpsKeyguardInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + burnInInteractor = + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ) + + underTest = + UdfpsKeyguardInteractor( + configRepository, + burnInInteractor, + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + } + + @Test + fun dozeChanges_updatesUdfpsAodModel() = + testScope.runTest { + val burnInOffsets by collectLastValue(underTest.burnInOffsets) + initializeBurnInOffsets() + + // WHEN we're not dozing + setAwake() + runCurrent() + + // THEN burn in offsets are 0 + assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f) + assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0) + assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0) + + // WHEN we're in the middle of the doze amount change + keyguardRepository.setDozeAmount(.50f) + runCurrent() + + // THEN burn in is updated (between 0 and the full offset) + assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f) + assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0) + assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0) + assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress) + assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset) + assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset) + + // WHEN we're fully dozing + keyguardRepository.setDozeAmount(1f) + runCurrent() + + // THEN burn in offsets are updated to final current values (for the given time) + assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress) + assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset) + assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset) + } + + private fun initializeBurnInOffsets() { + whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress) + whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true))) + .thenReturn(burnInXOffset) + whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false))) + .thenReturn(burnInYOffset) + } + + private fun setAwake() { + keyguardRepository.setDozeAmount(0f) + burnInInteractor.dozeTimeTick() + + bouncerRepository.setAlternateVisible(false) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + bouncerRepository.setPrimaryShow(false) + keyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.POWER_BUTTON, + ) + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt new file mode 100644 index 000000000000..2e97208e44de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.graphics.Point +import android.view.ViewGroup +import android.view.WindowManager +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.monet.utils.ArgbSubject.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class DefaultLockscreenLayoutTest : SysuiTestCase() { + private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + private lateinit var rootView: KeyguardRootView + @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + rootView = KeyguardRootView(context, null) + defaultLockscreenLayout = + DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context) + } + + @Test + fun testLayoutViews_KeyguardIndicationArea() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.keyguard_indication_area) + assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT) + assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) + } + + @Test + fun testLayoutViews_lockIconView() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + } + + @Test + fun testCenterLockIcon() { + defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + + assertThat(constraint.layout.mWidth).isEqualTo(2) + assertThat(constraint.layout.mHeight).isEqualTo(2) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.topMargin).isEqualTo(5) + assertThat(constraint.layout.startMargin).isEqualTo(4) + } + + /** Get the ConstraintLayout constraint of the view. */ + private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint { + val constraintSet = ConstraintSet() + constraintSet.clone(rootView) + return constraintSet.getConstraint(viewId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt new file mode 100644 index 000000000000..145b2fdb000f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import java.io.PrintWriter +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener + @Mock private lateinit var commandRegistry: CommandRegistry + @Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock private lateinit var pw: PrintWriter + private lateinit var command: () -> Command + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + keyguardLayoutManagerCommandListener = + KeyguardLayoutManagerCommandListener( + commandRegistry, + keyguardLayoutManager, + ) + keyguardLayoutManagerCommandListener.start() + command = + withArgCaptor<() -> Command> { + verify(commandRegistry).registerCommand(eq("layout"), capture()) + } + } + + @Test + fun testHelp() { + command().execute(pw, listOf("help")) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testBlank() { + command().execute(pw, listOf()) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testValidArg() { + bindFakeIdMapToLayoutManager() + command().execute(pw, listOf("fake")) + verify(keyguardLayoutManager).transitionToLayout("fake") + } + + private fun bindFakeIdMapToLayoutManager() { + val map = mapOf("fake" to mock(LockscreenLayout::class.java)) + whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt new file mode 100644 index 000000000000..95b2030de923 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock lateinit var configurationController: ConfigurationController + @Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + @Mock lateinit var keyguardRootView: KeyguardRootView + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT) + keyguardLayoutManager = + KeyguardLayoutManager( + configurationController, + setOf(defaultLockscreenLayout), + keyguardRootView + ) + } + + @Test + fun testDefaultLayout() { + keyguardLayoutManager.transitionToLayout(DEFAULT) + verify(defaultLockscreenLayout).layoutViews(keyguardRootView) + } + + @Test + fun testTransitionToLayout_validId() { + assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue() + } + @Test + fun testTransitionToLayout_invalidId() { + assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index a3413466d62e..c67f53519957 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -22,8 +22,18 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.mockito.mock import com.google.common.collect.Range @@ -47,7 +57,12 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) } @@ -60,7 +75,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -82,7 +97,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this) // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.5f)) @@ -104,7 +119,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.2f)) @@ -126,7 +141,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -138,13 +153,44 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } - private fun step( - value: Float, - state: TransitionState = TransitionState.RUNNING - ): TransitionStep { + @Test + fun transitionEnded() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<TransitionStep>() + + val job = underTest.transitionEnded.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 1.0f, CANCELED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 1.0f, FINISHED)) + + assertThat(values.size).isEqualTo(3) + values.forEach { + assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED) + .isTrue() + } + + job.cancel() + } + + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( - from = KeyguardState.DREAMING, - to = KeyguardState.LOCKSCREEN, + from = DREAMING, + to = LOCKSCREEN, value = value, transitionState = state, ownerName = "DreamingToLockscreenTransitionViewModelTest" diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt index 694539b0cbfe..75c8bff326b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = GoneToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 29886d5481b9..d02b3fccef1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -41,13 +41,12 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition @@ -210,10 +209,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, repository = repository, logger = UiEventLoggerFake(), featureFlags = featureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index ea17751782c4..12fe07f0827e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index bf56a981fa31..83ae631ed164 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToOccludedTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index 34da26ecc0bf..88603999cdd7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = OccludedToLockscreenTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index f88b71d469cf..d8c78ebdca49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.shared.model.TransitionState @@ -55,7 +55,12 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = PrimaryBouncerToGoneTransitionViewModel( interactor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt new file mode 100644 index 000000000000..436c09ca4a05 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@ExperimentalCoroutinesApi +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +class UdfpsAodViewModelTest : SysuiTestCase() { + private val defaultPadding = 12 + private lateinit var underTest: UdfpsAodViewModel + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding) + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + + val udfpsKeyguardInteractor = + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + + underTest = + UdfpsAodViewModel( + udfpsKeyguardInteractor, + context, + ) + } + + @Test + fun alphaAndVisibleUpdates_onDozeAmountChanges() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + val visible by collectLastValue(underTest.isVisible) + + keyguardRepository.setDozeAmount(0f) + runCurrent() + assertThat(alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + keyguardRepository.setDozeAmount(.65f) + runCurrent() + assertThat(alpha).isEqualTo(.65f) + assertThat(visible).isTrue() + + keyguardRepository.setDozeAmount(.23f) + runCurrent() + assertThat(alpha).isEqualTo(.23f) + assertThat(visible).isTrue() + + keyguardRepository.setDozeAmount(1f) + runCurrent() + assertThat(alpha).isEqualTo(1f) + assertThat(visible).isTrue() + } + + @Test + fun paddingUpdates_onScaleForResolutionChanges() = + testScope.runTest { + val padding by collectLastValue(underTest.padding) + + configRepository.setScaleForResolution(1f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding) + + configRepository.setScaleForResolution(2f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding * 2) + + configRepository.setScaleForResolution(.5f) + runCurrent() + assertThat(padding).isEqualTo((defaultPadding * .5f).toInt()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt new file mode 100644 index 000000000000..a30e2a601e9d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +/** Tests UdfpsFingerprintViewModel specific flows. */ +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsFingerprintViewModelTest : SysuiTestCase() { + private val defaultPadding = 12 + private lateinit var underTest: FingerprintViewModel + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding) + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + bouncerRepository = FakeKeyguardBouncerRepository() + transitionRepository = FakeKeyguardTransitionRepository() + val transitionInteractor = + KeyguardTransitionInteractor( + transitionRepository, + testScope.backgroundScope, + ) + val udfpsKeyguardInteractor = + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + + underTest = + FingerprintViewModel( + context, + transitionInteractor, + udfpsKeyguardInteractor, + ) + } + + @Test + fun paddingUpdates_onScaleForResolutionChanges() = + testScope.runTest { + val padding by collectLastValue(underTest.padding) + + configRepository.setScaleForResolution(1f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding) + + configRepository.setScaleForResolution(2f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding * 2) + + configRepository.setScaleForResolution(.5f) + runCurrent() + assertThat(padding).isEqualTo((defaultPadding * .5).toInt()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt new file mode 100644 index 000000000000..d58ceee40c68 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.Utils +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations + +/** Tests UDFPS lockscreen view model transitions. */ +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsLockscreenViewModelTest : SysuiTestCase() { + private val lockscreenColorResId = android.R.attr.textColorPrimary + private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed + private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId) + private val alternateBouncerColor = + Utils.getColorAttrDefaultColor(context, alternateBouncerResId) + + private lateinit var underTest: UdfpsLockscreenViewModel + private lateinit var testScope: TestScope + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testScope = TestScope() + transitionRepository = FakeKeyguardTransitionRepository() + val transitionInteractor = + KeyguardTransitionInteractor( + transitionRepository, + testScope.backgroundScope, + ) + underTest = + UdfpsLockscreenViewModel( + context, + lockscreenColorResId, + alternateBouncerResId, + transitionInteractor, + ) + } + + @Test + fun goneToAodTransition() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.RUNNING: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.FINISHED: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + } + + @Test + fun lockscreenToAod() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + } + + @Test + fun aodToLockscreen() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + + // TransitionState.RUNNING: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + } + + @Test + fun lockscreenToAlternateBouncer() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + } + + fun alternateBouncerToPrimaryBouncer() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isFalse() + } + + fun alternateBouncerToAod() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isFalse() + } + + @Test + fun lockscreenToOccluded() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + } + + @Test + fun occludedToLockscreen() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index b40ebc9bb156..91b0245be8d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -193,6 +193,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test + fun dozeWakeUpAnimationWaiting_inSplitShade_mediaIsHidden() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = true + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(splitShadeContainer.visibility).isEqualTo(GONE) + } + + @Test fun dozing_inSingleShade_mediaIsVisible() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) @@ -203,6 +214,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } + @Test + fun dozeWakeUpAnimationWaiting_inSingleShade_mediaIsVisible() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = false + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) + } + private fun setDozing() { whenever(statusBarStateController.isDozing).thenReturn(true) statusBarStateListener.onDozingChanged(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 7df54d44e69e..e4f89a226a34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -291,13 +291,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -525,16 +525,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT); assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo( TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue(); + assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt new file mode 100644 index 000000000000..bcbf666fd302 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator +import com.android.systemui.util.mockito.whenever +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() { + + @Mock private lateinit var flags: FeatureFlags + @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator + + private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags) + } + + @Test + fun start_flagEnabled_startsCoordinator() { + whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true) + + coreStartable.start() + + verify(coordinator).start() + } + + @Test + fun start_flagDisabled_doesNotStartCoordinator() { + whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false) + + coreStartable.start() + + verifyZeroInteractions(coordinator) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt new file mode 100644 index 000000000000..83932b0a6133 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.os.Binder +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() { + + private val fakeActivityTaskManager = FakeActivityTaskManager() + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + + private val repo = + ActivityTaskManagerTasksRepository( + activityTaskManager = fakeActivityTaskManager.activityTaskManager, + applicationScope = testScope.backgroundScope, + backgroundDispatcher = dispatcher + ) + + @Test + fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() { + fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2)) + + testScope.runTest { + val matchingTask = + repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder()) + + assertThat(matchingTask).isNull() + } + } + + @Test + fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() { + val expectedToken = createToken() + val expectedTask = createTask(taskId = 1, token = expectedToken) + + fakeActivityTaskManager.addRunningTasks( + createTask(taskId = 2), + expectedTask, + ) + + testScope.runTest { + val actualTask = + repo.findRunningTaskFromWindowContainerToken( + windowContainerToken = expectedToken.asBinder() + ) + + assertThat(actualTask).isEqualTo(expectedTask) + } + } + + @Test + fun foregroundTask_returnsStreamOfTasksMovedToFront() = + testScope.runTest { + val foregroundTask by collectLastValue(repo.foregroundTask) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + assertThat(foregroundTask?.taskId).isEqualTo(1) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2)) + assertThat(foregroundTask?.taskId).isEqualTo(2) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3)) + assertThat(foregroundTask?.taskId).isEqualTo(3) + } + + @Test + fun foregroundTask_lastValueIsCached() = + testScope.runTest { + val foregroundTaskA by collectLastValue(repo.foregroundTask) + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + assertThat(foregroundTaskA?.taskId).isEqualTo(1) + + val foregroundTaskB by collectLastValue(repo.foregroundTask) + assertThat(foregroundTaskB?.taskId).isEqualTo(1) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt new file mode 100644 index 000000000000..1c4870bc32b1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityTaskManager +import android.app.TaskStackListener +import android.content.Intent +import android.window.IWindowContainerToken +import android.window.WindowContainerToken +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever + +class FakeActivityTaskManager { + + private val runningTasks = mutableListOf<RunningTaskInfo>() + private val taskTaskListeners = mutableListOf<TaskStackListener>() + + val activityTaskManager = mock<ActivityTaskManager>() + + init { + whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer { + taskTaskListeners += it.arguments[0] as TaskStackListener + return@thenAnswer Unit + } + whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer { + taskTaskListeners -= it.arguments[0] as TaskStackListener + return@thenAnswer Unit + } + whenever(activityTaskManager.getTasks(any())).thenAnswer { + val maxNumTasks = it.arguments[0] as Int + return@thenAnswer runningTasks.take(maxNumTasks) + } + } + + fun moveTaskToForeground(task: RunningTaskInfo) { + taskTaskListeners.forEach { it.onTaskMovedToFront(task) } + } + + fun addRunningTasks(vararg tasks: RunningTaskInfo) { + runningTasks += tasks + } + + companion object { + + fun createTask( + taskId: Int, + token: WindowContainerToken = createToken(), + baseIntent: Intent = Intent() + ) = + RunningTaskInfo().apply { + this.taskId = taskId + this.token = token + this.baseIntent = baseIntent + } + + fun createToken(): WindowContainerToken { + val realToken = object : IWindowContainerToken.Stub() {} + return WindowContainerToken(realToken) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt new file mode 100644 index 000000000000..c59fd60cca9b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.app.TaskInfo +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeMediaProjectionRepository : MediaProjectionRepository { + + private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting) + + fun switchProjectedTask(newTask: TaskInfo) { + state.value = MediaProjectionState.SingleTask(newTask) + } + + override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow() + + fun projectEntireScreen() { + state.value = MediaProjectionState.EntireScreen + } + + fun stopProjecting() { + state.value = MediaProjectionState.NotProjecting + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt new file mode 100644 index 000000000000..593e3893fb2a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.content.Intent +import android.os.IBinder +import android.window.IWindowContainerToken +import android.window.WindowContainerToken +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeTasksRepository : TasksRepository { + + private val _foregroundTask = MutableStateFlow(DEFAULT_TASK) + + override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow() + + private val runningTasks = mutableListOf(DEFAULT_TASK) + + override suspend fun findRunningTaskFromWindowContainerToken( + windowContainerToken: IBinder + ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken } + + fun addRunningTask(task: RunningTaskInfo) { + runningTasks.add(task) + } + + fun moveTaskToForeground(task: RunningTaskInfo) { + _foregroundTask.value = task + } + + companion object { + val DEFAULT_TASK = createTask(taskId = -1) + val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + + fun createTask( + taskId: Int, + token: WindowContainerToken = createToken(), + baseIntent: Intent = Intent() + ) = + RunningTaskInfo().apply { + this.taskId = taskId + this.token = token + this.baseIntent = baseIntent + } + + fun createToken(): WindowContainerToken { + val realToken = object : IWindowContainerToken.Stub() {} + return WindowContainerToken(realToken) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt new file mode 100644 index 000000000000..2b074655bb02 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.media.projection.MediaProjectionInfo +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Handler +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import android.view.ContentRecordingSession +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MediaProjectionManagerRepositoryTest : SysuiTestCase() { + + private val mediaProjectionManager = mock<MediaProjectionManager>() + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + private val tasksRepo = FakeTasksRepository() + + private lateinit var callback: MediaProjectionManager.Callback + private lateinit var repo: MediaProjectionManagerRepository + + @Before + fun setUp() { + whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer { + callback = it.arguments[0] as MediaProjectionManager.Callback + return@thenAnswer Unit + } + repo = + MediaProjectionManagerRepository( + mediaProjectionManager = mediaProjectionManager, + handler = Handler.getMain(), + applicationScope = testScope.backgroundScope, + tasksRepository = tasksRepo + ) + } + + @Test + fun mediaProjectionState_onStart_emitsNotProjecting() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + callback.onStart(TEST_MEDIA_INFO) + + assertThat(state).isEqualTo(MediaProjectionState.NotProjecting) + } + + @Test + fun mediaProjectionState_onStop_emitsNotProjecting() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + callback.onStop(TEST_MEDIA_INFO) + + assertThat(state).isEqualTo(MediaProjectionState.NotProjecting) + } + + @Test + fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null) + + assertThat(state).isEqualTo(MediaProjectionState.NotProjecting) + } + + @Test + fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.EntireScreen) + } + + @Test + fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val session = + ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.EntireScreen) + } + + @Test + fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val taskWindowContainerToken = Binder() + val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.EntireScreen) + } + + @Test + fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() = + testScope.runTest { + val token = FakeTasksRepository.createToken() + val task = FakeTasksRepository.createTask(taskId = 1, token = token) + tasksRepo.addRunningTask(task) + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val session = ContentRecordingSession.createTaskSession(token.asBinder()) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task)) + } + + companion object { + val TEST_MEDIA_INFO = + MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt new file mode 100644 index 000000000000..112950b860e8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.domain.interactor + +import android.content.Intent +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TaskSwitchInteractorTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + + private val fakeActivityTaskManager = FakeActivityTaskManager() + private val mediaRepo = FakeMediaProjectionRepository() + private val tasksRepo = + ActivityTaskManagerTasksRepository( + activityTaskManager = fakeActivityTaskManager.activityTaskManager, + applicationScope = testScope.backgroundScope, + backgroundDispatcher = dispatcher + ) + + private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) + + @Test + fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() = + testScope.runTest { + mediaRepo.stopProjecting() + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask) + } + + @Test + fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() = + testScope.runTest { + mediaRepo.projectEntireScreen() + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1) + mediaRepo.switchProjectedTask(projectedTask) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(taskSwitchState) + .isEqualTo( + TaskSwitchState.TaskSwitched( + projectedTask = projectedTask, + foregroundTask = foregroundTask + ) + ) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT) + mediaRepo.switchProjectedTask(projectedTask) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 0) + mediaRepo.switchProjectedTask(projectedTask) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + companion object { + private val LAUNCHER_INTENT: Intent = + Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt new file mode 100644 index 000000000000..ea44fb3b1f6e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel + +import android.content.Intent +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + + private val fakeActivityTaskManager = FakeActivityTaskManager() + private val mediaRepo = FakeMediaProjectionRepository() + private val tasksRepo = + ActivityTaskManagerTasksRepository( + activityTaskManager = fakeActivityTaskManager.activityTaskManager, + applicationScope = testScope.backgroundScope, + backgroundDispatcher = dispatcher + ) + private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) + + private val viewModel = TaskSwitcherNotificationViewModel(interactor) + + @Test + fun uiState_notProjecting_emitsNotShowing() = + testScope.runTest { + mediaRepo.stopProjecting() + val uiState by collectLastValue(viewModel.uiState) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() = + testScope.runTest { + mediaRepo.stopProjecting() + val uiState by collectLastValue(viewModel.uiState) + + mediaRepo.switchProjectedTask(createTask(taskId = 1)) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingEntireScreen_emitsNotShowing() = + testScope.runTest { + mediaRepo.projectEntireScreen() + val uiState by collectLastValue(viewModel.uiState) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() = + testScope.runTest { + mediaRepo.projectEntireScreen() + val uiState by collectLastValue(viewModel.uiState) + + mediaRepo.switchProjectedTask(createTask(taskId = 1)) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + mediaRepo.switchProjectedTask(projectedTask) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(uiState) + .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask)) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + mediaRepo.switchProjectedTask(projectedTask) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.moveTaskToForeground(projectedTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT) + mediaRepo.switchProjectedTask(projectedTask) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + companion object { + private val LAUNCHER_INTENT: Intent = + Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt index 9e5422470d8b..5890cbd06476 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -97,10 +97,11 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { multiShadeInteractor = interactor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = keyguardTransitionRepository, - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, falsingManager = falsingManager, shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 962b53737274..22b1c7b58ab3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -204,6 +204,19 @@ public class QSTileImplTest extends SysuiTestCase { } @Test + public void testLongClick_falsing() { + mFalsingManager.setFalseLongTap(true); + mTile.longClick(null /* view */); + mTestableLooper.processAllMessages(); + assertThat(mTile.mLongClicked).isFalse(); + + mFalsingManager.setFalseLongTap(false); + mTile.longClick(null /* view */); + mTestableLooper.processAllMessages(); + assertThat(mTile.mLongClicked).isTrue(); + } + + @Test public void testSecondaryClick_Metrics() { mTile.secondaryClick(null /* view */); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK))); @@ -518,6 +531,7 @@ public class QSTileImplTest extends SysuiTestCase { } private static class TileImpl extends QSTileImpl<QSTile.BooleanState> { boolean mClicked; + boolean mLongClicked; int mRefreshes = 0; protected TileImpl( @@ -551,6 +565,11 @@ public class QSTileImplTest extends SysuiTestCase { } @Override + protected void handleLongClick(@Nullable View view) { + mLongClicked = true; + } + + @Override protected void handleUpdateState(BooleanState state, Object arg) { mRefreshes++; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index ef7c7bc0844d..9188293dc751 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -80,6 +80,7 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.common.ui.view.LongPressHandlingView; @@ -91,7 +92,6 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -629,7 +629,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mHeadsUpManager); mNotificationPanelViewController.setTrackingStartedListener(() -> {}); mNotificationPanelViewController.setOpenCloseListener( - new NotificationPanelViewController.OpenCloseListener() { + new OpenCloseListener() { @Override public void onClosingFinished() {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 5802eb3d9618..eb4ae1a743ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -514,6 +514,17 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() { + when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); + mStatusBarStateController.setState(KEYGUARD); + enableSplitShade(/* enabled= */ true); + + mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true); + + verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true); + } + + @Test public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); mStatusBarStateController.setState(KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 2a9b403cb2e6..5fb3a7955b5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -28,6 +28,11 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.bouncer.data.factory.BouncerMessageFactory +import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository +import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor +import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil +import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.dock.DockManager @@ -35,14 +40,9 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.bouncer.data.factory.BouncerMessageFactory -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor -import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil -import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -80,9 +80,9 @@ import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import java.util.Optional +import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -198,10 +198,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 252a03bb07d2..544137e95779 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -40,8 +40,8 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -211,10 +211,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 00a056708f07..729c4a9145c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -56,7 +56,7 @@ class ShadeControllerImplTest : SysuiTestCase() { @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var assistManager: AssistManager @Mock private lateinit var gutsManager: NotificationGutsManager - @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock private lateinit var shadeViewController: ShadeViewController @Mock private lateinit var nswvc: NotificationShadeWindowViewController @Mock private lateinit var display: Display @@ -82,7 +82,7 @@ class ShadeControllerImplTest : SysuiTestCase() { Lazy { gutsManager }, ) shadeController.setNotificationShadeWindowViewController(nswvc) - shadeController.setNotificationPanelViewController(notificationPanelViewController) + shadeController.setShadeViewController(shadeViewController) } @Test @@ -91,9 +91,9 @@ class ShadeControllerImplTest : SysuiTestCase() { // Trying to open it does nothing. shadeController.animateExpandShade() - verify(notificationPanelViewController, never()).expandToNotifications() + verify(shadeViewController, never()).expandToNotifications() shadeController.animateExpandQs() - verify(notificationPanelViewController, never()).expand(ArgumentMatchers.anyBoolean()) + verify(shadeViewController, never()).expand(ArgumentMatchers.anyBoolean()) } @Test @@ -102,15 +102,15 @@ class ShadeControllerImplTest : SysuiTestCase() { // Can now be opened. shadeController.animateExpandShade() - verify(notificationPanelViewController).expandToNotifications() + verify(shadeViewController).expandToNotifications() shadeController.animateExpandQs() - verify(notificationPanelViewController).expandToQs() + verify(shadeViewController).expandToQs() } @Test fun cancelExpansionAndCollapseShade_callsCancelCurrentTouch() { // GIVEN the shade is tracking a touch - whenever(notificationPanelViewController.isTracking).thenReturn(true) + whenever(shadeViewController.isTracking).thenReturn(true) // WHEN cancelExpansionAndCollapseShade is called shadeController.cancelExpansionAndCollapseShade() @@ -122,7 +122,7 @@ class ShadeControllerImplTest : SysuiTestCase() { @Test fun cancelExpansionAndCollapseShade_doesNotCallAnimateCollapseShade_whenCollapsed() { // GIVEN the shade is tracking a touch - whenever(notificationPanelViewController.isTracking).thenReturn(false) + whenever(shadeViewController.isTracking).thenReturn(false) // WHEN cancelExpansionAndCollapseShade is called shadeController.cancelExpansionAndCollapseShade() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index 45247977283a..3ea8f5412b40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -84,6 +84,7 @@ class ShadeInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) + featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val refreshUsersScheduler = RefreshUsersScheduler( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt new file mode 100644 index 000000000000..cfbe8e36537d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class CommandParserTest : SysuiTestCase() { + private val parser = CommandParser() + + @Test + fun registerToken_cannotReuseNames() { + parser.flag("-f") + assertThrows(IllegalArgumentException::class.java) { parser.flag("-f") } + } + + @Test + fun unknownToken_throws() { + assertThrows(ArgParseError::class.java) { parser.parse(listOf("unknown-token")) } + } + + @Test + fun parseSingleFlag_present() { + val flag by parser.flag("-f") + parser.parse(listOf("-f")) + assertTrue(flag) + } + + @Test + fun parseSingleFlag_notPresent() { + val flag by parser.flag("-f") + parser.parse(listOf()) + assertFalse(flag) + } + + @Test + fun parseSingleOptionalParam_present() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parse(listOf("-p", "123")) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseSingleOptionalParam_notPresent() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parse(listOf()) + assertThat(param).isNull() + } + + @Test + fun parseSingleOptionalParam_missingArg_throws() { + val param by parser.param("-p", valueParser = Type.Int) + assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) } + } + + @Test + fun parseSingleRequiredParam_present() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + parser.parse(listOf("-p", "123")) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseSingleRequiredParam_notPresent_failsValidation() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + assertFalse(parser.parse(listOf())) + } + + @Test + fun parseSingleRequiredParam_missingArg_throws() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) } + } + + @Test + fun parseAsSubCommand_singleFlag_present() { + val flag by parser.flag("-f") + val args = listOf("-f").listIterator() + parser.parseAsSubCommand(args) + + assertTrue(flag) + } + + @Test + fun parseAsSubCommand_singleFlag_notPresent() { + val flag by parser.flag("-f") + val args = listOf("--other-flag").listIterator() + parser.parseAsSubCommand(args) + + assertFalse(flag) + } + + @Test + fun parseAsSubCommand_singleOptionalParam_present() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator()) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseAsSubCommand_singleOptionalParam_notPresent() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator()) + assertThat(param).isNull() + } + + @Test + fun parseAsSubCommand_singleRequiredParam_present() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator()) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseAsSubCommand_singleRequiredParam_notPresent() { + parser.require(parser.param("-p", valueParser = Type.Int)) + assertFalse(parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator())) + } + + @Test + fun parseCommandWithSubCommand_required_provided() { + val topLevelFlag by parser.flag("flag", shortName = "-f") + + val cmd = + object : ParseableCommand("test") { + val flag by flag("flag1") + override fun execute(pw: PrintWriter) {} + } + + parser.require(parser.subCommand(cmd)) + parser.parse(listOf("-f", "test", "--flag1")) + + assertTrue(topLevelFlag) + assertThat(cmd).isNotNull() + assertTrue(cmd.flag) + } + + @Test + fun parseCommandWithSubCommand_required_notProvided() { + val topLevelFlag by parser.flag("-f") + + val cmd = + object : ParseableCommand("test") { + val flag by parser.flag("flag1") + override fun execute(pw: PrintWriter) {} + } + + parser.require(parser.subCommand(cmd)) + + assertFalse(parser.parse(listOf("-f"))) + } + + @Test + fun flag_requiredParam_optionalParam_allProvided_failsValidation() { + val flag by parser.flag("-f") + val optionalParam by parser.param("-p", valueParser = Type.Int) + val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean)) + + parser.parse( + listOf( + "-f", + "-p", + "123", + "-p2", + "false", + ) + ) + + assertTrue(flag) + assertThat(optionalParam).isEqualTo(123) + assertFalse(requiredParam) + } + + @Test + fun flag_requiredParam_optionalParam_optionalExcluded() { + val flag by parser.flag("-f") + val optionalParam by parser.param("-p", valueParser = Type.Int) + val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean)) + + parser.parse( + listOf( + "-p2", + "true", + ) + ) + + assertFalse(flag) + assertThat(optionalParam).isNull() + assertTrue(requiredParam) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt new file mode 100644 index 000000000000..e391d6b11cd6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt @@ -0,0 +1,55 @@ +package com.android.systemui.statusbar.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class ParametersTest : SysuiTestCase() { + @Test + fun singleArgOptional_returnsNullBeforeParse() { + val optional by SingleArgParamOptional(longName = "longName", valueParser = Type.Int) + assertThat(optional).isNull() + } + + @Test + fun singleArgOptional_returnsParsedValue() { + val param = SingleArgParamOptional(longName = "longName", valueParser = Type.Int) + param.parseArgsFromIter(listOf("3").listIterator()) + val optional by param + assertThat(optional).isEqualTo(3) + } + + @Test + fun singleArgRequired_throwsBeforeParse() { + val req by SingleArgParam(longName = "param", valueParser = Type.Boolean) + assertThrows(IllegalStateException::class.java) { req } + } + + @Test + fun singleArgRequired_returnsParsedValue() { + val param = SingleArgParam(longName = "param", valueParser = Type.Boolean) + param.parseArgsFromIter(listOf("true").listIterator()) + val req by param + assertTrue(req) + } + + @Test + fun param_handledAfterParse() { + val optParam = SingleArgParamOptional(longName = "string1", valueParser = Type.String) + val reqParam = SingleArgParam(longName = "string2", valueParser = Type.Float) + + assertFalse(optParam.handled) + assertFalse(reqParam.handled) + + optParam.parseArgsFromIter(listOf("test").listIterator()) + reqParam.parseArgsFromIter(listOf("1.23").listIterator()) + + assertTrue(optParam.handled) + assertTrue(reqParam.handled) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt new file mode 100644 index 000000000000..86548d079003 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +class ParseableCommandTest : SysuiTestCase() { + @Mock private lateinit var pw: PrintWriter + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + /** + * A little change-detector-y, but this is just a general assertion that building up a command + * parser via its wrapper works as expected. + */ + @Test + fun testFactoryMethods() { + val mySubCommand = + object : ParseableCommand("subCommand") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + val mySubCommand2 = + object : ParseableCommand("subCommand2") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + // Verify that the underlying parser contains the correct types + val myCommand = + object : ParseableCommand("testName") { + val flag by flag("flag", shortName = "f") + val requiredParam by + param(longName = "required-param", shortName = "r", valueParser = Type.String) + .required() + val optionalParam by + param(longName = "optional-param", shortName = "o", valueParser = Type.Boolean) + val optionalSubCommand by subCommand(mySubCommand) + val requiredSubCommand by subCommand(mySubCommand2).required() + + override fun execute(pw: PrintWriter) {} + } + + val flags = myCommand.parser.flags + val params = myCommand.parser.params + val subCommands = myCommand.parser.subCommands + + assertThat(flags).hasSize(2) + assertThat(flags[0]).isInstanceOf(Flag::class.java) + assertThat(flags[1]).isInstanceOf(Flag::class.java) + + assertThat(params).hasSize(2) + val req = params.filter { it is SingleArgParam<*> } + val opt = params.filter { it is SingleArgParamOptional<*> } + assertThat(req).hasSize(1) + assertThat(opt).hasSize(1) + + val reqSub = subCommands.filter { it is RequiredSubCommand<*> } + val optSub = subCommands.filter { it is OptionalSubCommand<*> } + assertThat(reqSub).hasSize(1) + assertThat(optSub).hasSize(1) + } + + @Test + fun factoryMethods_enforceShortNameRules() { + // Short names MUST be one character long + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val flag by flag("longName", "invalidShortName") + + override fun execute(pw: PrintWriter) {} + } + } + + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val param by param("longName", "invalidShortName", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + } + } + + @Test + fun factoryMethods_enforceLongNames_notPrefixed() { + // Long names must not start with "-", since they will be added + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val flag by flag("--invalid") + + override fun execute(pw: PrintWriter) {} + } + } + + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val param by param("-invalid", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + } + } + + @Test + fun executeDoesNotPropagateExceptions() { + val cmd = + object : ParseableCommand("test-command") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + val throwingCommand = listOf("unknown-token") + + // Given a command that would cause an ArgParseError + assertThrows(ArgParseError::class.java) { cmd.parser.parse(throwingCommand) } + + // The parser consumes that error + cmd.execute(pw, throwingCommand) + } + + @Test + fun executeFailingCommand_callsOnParseFailed() { + val cmd = + object : ParseableCommand("test-command") { + val flag by flag("flag") + + var onParseFailedCalled = false + + override fun execute(pw: PrintWriter) {} + override fun onParseFailed(error: ArgParseError) { + onParseFailedCalled = true + } + } + + val throwingCommand = listOf("unknown-token") + cmd.execute(pw, throwingCommand) + + assertTrue(cmd.onParseFailedCalled) + } + + @Test + fun baseCommand() { + val myCommand = MyCommand() + myCommand.execute(pw, baseCommand) + + assertThat(myCommand.flag1).isFalse() + assertThat(myCommand.singleParam).isNull() + } + + @Test + fun commandWithFlags() { + val command = MyCommand() + command.execute(pw, cmdWithFlags) + + assertThat(command.flag1).isTrue() + assertThat(command.flag2).isTrue() + } + + @Test + fun commandWithArgs() { + val cmd = MyCommand() + cmd.execute(pw, cmdWithSingleArgParam) + + assertThat(cmd.singleParam).isEqualTo("single_param") + } + + @Test + fun commandWithRequiredParam_provided() { + val cmd = + object : ParseableCommand(name) { + val singleRequiredParam: String by + param( + longName = "param1", + shortName = "p", + valueParser = Type.String, + ) + .required() + + override fun execute(pw: PrintWriter) {} + } + + val cli = listOf("-p", "value") + cmd.execute(pw, cli) + + assertThat(cmd.singleRequiredParam).isEqualTo("value") + } + + @Test + fun commandWithRequiredParam_not_provided_throws() { + val cmd = + object : ParseableCommand(name) { + val singleRequiredParam by + param(shortName = "p", longName = "param1", valueParser = Type.String) + .required() + + override fun execute(pw: PrintWriter) {} + + override fun execute(pw: PrintWriter, args: List<String>) { + parser.parse(args) + execute(pw) + } + } + + val cli = listOf("") + assertThrows(ArgParseError::class.java) { cmd.execute(pw, cli) } + } + + @Test + fun commandWithSubCommand() { + val subName = "sub-command" + val subCmd = + object : ParseableCommand(subName) { + val singleOptionalParam: String? by param("param", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + + val cmd = + object : ParseableCommand(name) { + val subCmd by subCommand(subCmd) + override fun execute(pw: PrintWriter) {} + } + + cmd.execute(pw, listOf("sub-command", "--param", "test")) + assertThat(cmd.subCmd?.singleOptionalParam).isEqualTo("test") + } + + @Test + fun complexCommandWithSubCommands_reusedNames() { + val commandLine = "-f --param1 arg1 sub-command1 -f -p arg2 --param2 arg3".split(" ") + + val subName = "sub-command1" + val subCmd = + object : ParseableCommand(subName) { + val flag1 by flag("flag", shortName = "f") + val param1: String? by param("param1", shortName = "p", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + + val myCommand = + object : ParseableCommand(name) { + val flag1 by flag(longName = "flag", shortName = "f") + val param1 by param("param1", shortName = "p", valueParser = Type.String).required() + val param2: String? by param(longName = "param2", valueParser = Type.String) + val subCommand by subCommand(subCmd) + + override fun execute(pw: PrintWriter) {} + } + + myCommand.execute(pw, commandLine) + + assertThat(myCommand.flag1).isTrue() + assertThat(myCommand.param1).isEqualTo("arg1") + assertThat(myCommand.param2).isEqualTo("arg3") + assertThat(myCommand.subCommand).isNotNull() + assertThat(myCommand.subCommand?.flag1).isTrue() + assertThat(myCommand.subCommand?.param1).isEqualTo("arg2") + } + + class MyCommand( + private val onExecute: ((MyCommand) -> Unit)? = null, + ) : ParseableCommand(name) { + + val flag1 by flag(shortName = "f", longName = "flag1", description = "flag 1 for test") + val flag2 by flag(shortName = "g", longName = "flag2", description = "flag 2 for test") + val singleParam: String? by + param( + shortName = "a", + longName = "arg1", + valueParser = Type.String, + ) + + override fun execute(pw: PrintWriter) { + onExecute?.invoke(this) + } + } + + companion object { + const val name = "my_command" + val baseCommand = listOf("") + val cmdWithFlags = listOf("-f", "--flag2") + val cmdWithSingleArgParam = listOf("--arg1", "single_param") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt new file mode 100644 index 000000000000..759f0bcd6ea8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt @@ -0,0 +1,61 @@ +package com.android.systemui.statusbar.commandline + +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class ValueParserTest : SysuiTestCase() { + @Test + fun parseString() { + assertThat(Type.String.parseValue("test")).isEqualTo(Result.success("test")) + } + + @Test + fun parseInt() { + assertThat(Type.Int.parseValue("123")).isEqualTo(Result.success(123)) + + assertTrue(Type.Int.parseValue("not an Int").isFailure) + } + + @Test + fun parseFloat() { + assertThat(Type.Float.parseValue("1.23")).isEqualTo(Result.success(1.23f)) + + assertTrue(Type.Int.parseValue("not a Float").isFailure) + } + + @Test + fun parseBoolean() { + assertThat(Type.Boolean.parseValue("true")).isEqualTo(Result.success(true)) + assertThat(Type.Boolean.parseValue("false")).isEqualTo(Result.success(false)) + + assertTrue(Type.Boolean.parseValue("not a Boolean").isFailure) + } + + @Test + fun mapToComplexType() { + val parseSquare = Type.Int.map { Rect(it, it, it, it) } + + assertThat(parseSquare.parseValue("10")).isEqualTo(Result.success(Rect(10, 10, 10, 10))) + } + + @Test + fun mapToFallibleComplexType() { + val fallibleParseSquare = + Type.Int.map { + if (it > 0) { + Rect(it, it, it, it) + } else { + null + } + } + + assertThat(fallibleParseSquare.parseValue("10")) + .isEqualTo(Result.success(Rect(10, 10, 10, 10))) + assertTrue(fallibleParseSquare.parseValue("-10").isFailure) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 6ae7dca296c8..ee8325ec02b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; +import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -415,6 +416,36 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + public void callSwipeCallbacksDuringClearAll() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(true); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test + public void callSwipeCallbacksDuringClearNotification() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(false); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test public void testOnMenuClickedLogging() { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 1ffffe4dca75..88d8dfc50b47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -450,7 +450,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { () -> mAssistManager, () -> mNotificationGutsManager )); - mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setShadeViewController(mNotificationPanelViewController); mShadeController.setNotificationShadeWindowViewController( mNotificationShadeWindowViewController); mShadeController.setNotificationPresenter(mNotificationPresenter); @@ -490,9 +490,11 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mMetricsLogger, mShadeLogger, mUiBgExecutor, + mNotificationPanelViewController, mNotificationMediaManager, mLockscreenUserManager, mRemoteInputManager, + mQuickSettingsController, mUserSwitcherController, mBatteryController, mColorExtractor, @@ -587,8 +589,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // TODO: we should be able to call mCentralSurfaces.start() and have all the below values // initialized automatically and make NPVC private. mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView; - mCentralSurfaces.mShadeSurface = mNotificationPanelViewController; - mCentralSurfaces.mQsController = mQuickSettingsController; mCentralSurfaces.mDozeScrimController = mDozeScrimController; mCentralSurfaces.mPresenter = mNotificationPresenter; mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt new file mode 100644 index 000000000000..47671fbadd0a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone + +import android.app.WallpaperManager +import android.content.pm.UserInfo +import android.os.Looper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.user.data.model.SelectionStatus +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.os.FakeHandler +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.verify + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class LockscreenWallpaperTest : SysuiTestCase() { + + private lateinit var underTest: LockscreenWallpaper + + private val testScope = TestScope(StandardTestDispatcher()) + private val userRepository = FakeUserRepository() + + private val wallpaperManager: WallpaperManager = mock() + + @Before + fun setUp() { + whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false) + whenever(wallpaperManager.isWallpaperSupported).thenReturn(true) + underTest = + LockscreenWallpaper( + /* wallpaperManager= */ wallpaperManager, + /* iWallpaperManager= */ mock(), + /* keyguardUpdateMonitor= */ mock(), + /* dumpManager= */ mock(), + /* mediaManager= */ mock(), + /* mainHandler= */ FakeHandler(Looper.getMainLooper()), + /* javaAdapter= */ JavaAdapter(testScope.backgroundScope), + /* userRepository= */ userRepository, + /* userTracker= */ mock(), + ) + underTest.start() + } + + @Test + fun getBitmap_matchesUserIdFromUserRepo() = + testScope.runTest { + val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + userRepository.setUserInfos(listOf(info)) + userRepository.setSelectedUserInfo(info) + + underTest.bitmap + + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } + + @Test + fun getBitmap_usesOldUserIfNewUserInProgress() = + testScope.runTest { + val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0) + userRepository.setUserInfos(listOf(info5, info6)) + userRepository.setSelectedUserInfo(info5) + + // WHEN the selection of user 6 is only in progress + userRepository.setSelectedUserInfo( + info6, + selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS + ) + + underTest.bitmap + + // THEN we still use user 5 for wallpaper selection + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 6301fa0be463..842d548c8358 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -48,7 +48,11 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { keyguardTransitionRepository = FakeKeyguardTransitionRepository() val interactor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 079fbcd0304c..0c28cbb52831 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -26,6 +26,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat @@ -224,6 +226,40 @@ class UserRepositoryImplTest : SysuiTestCase() { } @Test + fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest { + underTest = create(this) + var selectedUser: SelectedUserModel? = null + underTest.selectedUser.onEach { selectedUser = it }.launchIn(this) + setUpUsers(count = 2, selectedIndex = 1) + + // WHEN the user is changing + tracker.onUserChanging(userId = 1) + + // THEN the selection status is IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN the user has finished changing + tracker.onUserChanged(userId = 1) + + // THEN the selection status is COMPLETE + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + tracker.onProfileChanged() + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + setUpUsers(count = 2, selectedIndex = 0) + + tracker.onUserChanging(userId = 0) + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN a profile change occurs while a user is changing + tracker.onProfileChanged() + + // THEN the selection status remains as IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + } + + @Test fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest { underTest = create(this) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt new file mode 100644 index 000000000000..312ade510784 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import kotlinx.coroutines.CoroutineScope + +/** + * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+ + * tests whenever we add a constructor param. + */ +object KeyguardTransitionInteractorFactory { + @JvmOverloads + @JvmStatic + fun create( + scope: CoroutineScope, + repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(), + ): WithDependencies { + return WithDependencies( + repository = repository, + KeyguardTransitionInteractor( + scope = scope, + repository = repository, + ) + ) + } + + data class WithDependencies( + val repository: KeyguardTransitionRepository, + val keyguardTransitionInteractor: KeyguardTransitionInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 61e5b5fc27ea..51ee0c00cb0d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -19,18 +19,28 @@ package com.android.systemui.user.data.repository import android.content.pm.UserInfo import android.os.UserHandle +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.yield class FakeUserRepository : UserRepository { companion object { // User id to represent a non system (human) user id. We presume this is the main user. private const val MAIN_USER_ID = 10 + + private val DEFAULT_SELECTED_USER = 0 + private val DEFAULT_SELECTED_USER_INFO = + UserInfo( + /* id= */ DEFAULT_SELECTED_USER, + /* name= */ "default selected user", + /* flags= */ 0, + ) } private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel()) @@ -40,8 +50,11 @@ class FakeUserRepository : UserRepository { private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList()) override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() + override val selectedUser = + MutableStateFlow( + SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE) + ) + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } private val _userSwitchingInProgress = MutableStateFlow(false) override val userSwitchingInProgress: Flow<Boolean> @@ -72,7 +85,7 @@ class FakeUserRepository : UserRepository { } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -87,12 +100,15 @@ class FakeUserRepository : UserRepository { _userInfos.value = infos } - suspend fun setSelectedUserInfo(userInfo: UserInfo) { + suspend fun setSelectedUserInfo( + userInfo: UserInfo, + selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE, + ) { check(_userInfos.value.contains(userInfo)) { "Cannot select the following user, it is not in the list of user infos: $userInfo!" } - _selectedUserInfo.value = userInfo + selectedUser.value = SelectedUserModel(userInfo, selectionStatus) yield() } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index b1cdc5053c27..ba3d4340a157 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -319,6 +319,10 @@ public class FullScreenMagnificationController implements FullScreenMagnificationController::onUserContextChanged, FullScreenMagnificationController.this, mDisplayId); mControllerCtx.getHandler().sendMessage(m); + + synchronized (mLock) { + refreshThumbnail(); + } } @Override @@ -344,7 +348,7 @@ public class FullScreenMagnificationController implements mMagnificationRegion.set(magnified); mMagnificationRegion.getBounds(mMagnificationBounds); - refreshThumbnail(getScale(), getCenterX(), getCenterY()); + refreshThumbnail(); // It's possible that our magnification spec is invalid with the new bounds. // Adjust the current spec's offsets if necessary. @@ -602,13 +606,13 @@ public class FullScreenMagnificationController implements } @GuardedBy("mLock") - void refreshThumbnail(float scale, float centerX, float centerY) { + void refreshThumbnail() { if (mMagnificationThumbnail != null) { mMagnificationThumbnail.setThumbnailBounds( mMagnificationBounds, - scale, - centerX, - centerY + getScale(), + getCenterX(), + getCenterY() ); } } @@ -627,7 +631,7 @@ public class FullScreenMagnificationController implements // We call refreshThumbnail when the thumbnail is just created to set current // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet // updated properly and thus shows with huge size. (b/276314641) - refreshThumbnail(getScale(), getCenterX(), getCenterY()); + refreshThumbnail(); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 87fbee71ab59..f6948e9cbb3b 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -502,6 +502,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb @Override public void onSourceBoundsChanged(int displayId, Rect bounds) { if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) { + // notify sysui the magnification scale changed on window magnifier + mWindowMagnificationMgr.onUserMagnificationScaleChanged( + mUserId, displayId, getWindowMagnificationMgr().getScale(displayId)); + final MagnificationConfig config = new MagnificationConfig.Builder() .setMode(MAGNIFICATION_MODE_WINDOW) .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) @@ -516,6 +520,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region, @NonNull MagnificationConfig config) { if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) { + // notify sysui the magnification scale changed on fullscreen magnifier + mWindowMagnificationMgr.onUserMagnificationScaleChanged( + mUserId, displayId, config.getScale()); + mAms.notifyMagnificationChanged(displayId, region, config); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java index 03fa93d8a3bc..a7bdd5a09ac2 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java @@ -99,15 +99,17 @@ public class MagnificationThumbnail { Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds); } mHandler.post(() -> { - mWindowBounds = currentBounds; - setBackgroundBounds(); + refreshBackgroundBounds(currentBounds); if (mVisible) { updateThumbnailMainThread(scale, centerX, centerY); } }); } - private void setBackgroundBounds() { + @MainThread + private void refreshBackgroundBounds(Rect currentBounds) { + mWindowBounds = currentBounds; + Point magnificationBoundary = getMagnificationThumbnailPadding(mContext); mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO); mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO); @@ -117,6 +119,10 @@ public class MagnificationThumbnail { mBackgroundParams.height = mThumbnailHeight; mBackgroundParams.x = initX; mBackgroundParams.y = initY; + + if (mVisible) { + mWindowManager.updateViewLayout(mThumbnailLayout, mBackgroundParams); + } } @MainThread @@ -264,21 +270,16 @@ public class MagnificationThumbnail { mThumbnailView.setScaleX(scaleDown); mThumbnailView.setScaleY(scaleDown); } - float thumbnailWidth; - float thumbnailHeight; - if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) { - // if the thumbnail view size is not updated correctly, we just use the cached values. - thumbnailWidth = mThumbnailWidth; - thumbnailHeight = mThumbnailHeight; - } else { - thumbnailWidth = mThumbnailView.getWidth(); - thumbnailHeight = mThumbnailView.getHeight(); - } - if (!Float.isNaN(centerX)) { + + if (!Float.isNaN(centerX) + && !Float.isNaN(centerY) + && mThumbnailWidth > 0 + && mThumbnailHeight > 0 + ) { var padding = mThumbnailView.getPaddingTop(); var ratio = 1f / BG_ASPECT_RATIO; - var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding); - var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding); + var centerXScaled = centerX * ratio - (mThumbnailWidth / 2f + padding); + var centerYScaled = centerY * ratio - (mThumbnailHeight / 2f + padding); if (DEBUG) { Log.d( diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java index 1202cfadb44e..6c6394faa09c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java @@ -201,6 +201,22 @@ class WindowMagnificationConnectionWrapper { return true; } + boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) { + if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { + mTrace.logTrace(TAG + ".onMagnificationScaleUpdated", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId); + } + try { + mConnection.onUserMagnificationScaleChanged(userId, displayId, scale); + } catch (RemoteException e) { + if (DBG) { + Slog.e(TAG, "Error calling onMagnificationScaleUpdated()", e); + } + return false; + } + return true; + } + boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) { if (mTrace.isA11yTracingEnabledForTypes( FLAGS_WINDOW_MAGNIFICATION_CONNECTION diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index d07db3f7578d..d96682c5529d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -827,6 +827,20 @@ public class WindowMagnificationManager implements } /** + * Notify System UI the magnification scale on the specified display for userId is changed. + * + * @param userId the user id. + * @param displayId the logical display id. + * @param scale magnification scale. + */ + public boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) { + synchronized (mLock) { + return mConnectionWrapper != null + && mConnectionWrapper.onUserMagnificationScaleChanged(userId, displayId, scale); + } + } + + /** * Returns the screen-relative X coordinate of the center of the magnified bounds. * * @param displayId The logical display id diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 76a994ec63e9..99c2f8a5cc56 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1972,7 +1972,7 @@ class UserController implements Handler.Callback { } private void dismissUserSwitchDialog(Runnable onDismissed) { - mInjector.dismissUserSwitchingDialog(onDismissed); + mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed)); } private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 29a19417b8fd..393e43008fd0 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -30,6 +30,8 @@ import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; @@ -289,37 +291,38 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param on * @param eventSource for logging purposes */ - /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) { + /*package*/ void setSpeakerphoneOn( + IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid); + Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid); } postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( - cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), - on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); + cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), + on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged)); } /** * Select device for use for communication use cases. * @param cb Client binder for death detection - * @param pid Client pid + * @param uid Client uid * @param device Device selected or null to unselect. * @param eventSource for logging purposes */ private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000; - /*package*/ boolean setCommunicationDevice( - IBinder cb, int pid, AudioDeviceInfo device, String eventSource) { + /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device, + boolean isPrivileged, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid); + Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid); } AudioDeviceAttributes deviceAttr = (device != null) ? new AudioDeviceAttributes(device) : null; - CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr, - device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true); + CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr, + device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true, isPrivileged); postSetCommunicationDeviceForClient(deviceInfo); boolean status; synchronized (deviceInfo) { @@ -353,7 +356,7 @@ import java.util.concurrent.atomic.AtomicBoolean; Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo); } if (!deviceInfo.mOn) { - CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid); + CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid); if (client == null || (deviceInfo.mDevice != null && !deviceInfo.mDevice.equals(client.getDevice()))) { return false; @@ -361,22 +364,23 @@ import java.util.concurrent.atomic.AtomicBoolean; } AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null; - setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device, - deviceInfo.mScoAudioMode, deviceInfo.mEventSource); + setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device, + deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource); return true; } @GuardedBy("mDeviceStateLock") /*package*/ void setCommunicationRouteForClient( - IBinder cb, int pid, AudioDeviceAttributes device, - int scoAudioMode, String eventSource) { + IBinder cb, int uid, AudioDeviceAttributes device, + int scoAudioMode, boolean isPrivileged, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "setCommunicationRouteForClient: device: " + device); + Log.v(TAG, "setCommunicationRouteForClient: device: " + device + + ", eventSource: " + eventSource); } AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "setCommunicationRouteForClient for pid: " + pid - + " device: " + device + "setCommunicationRouteForClient for uid: " + uid + + " device: " + device + " isPrivileged: " + isPrivileged + " from API: " + eventSource)).printLog(TAG)); final boolean wasBtScoRequested = isBluetoothScoRequested(); @@ -385,16 +389,18 @@ import java.util.concurrent.atomic.AtomicBoolean; // Save previous client route in case of failure to start BT SCO audio AudioDeviceAttributes prevClientDevice = null; - client = getCommunicationRouteClientForPid(pid); + boolean prevPrivileged = false; + client = getCommunicationRouteClientForUid(uid); if (client != null) { prevClientDevice = client.getDevice(); + prevPrivileged = client.isPrivileged(); } if (device != null) { - client = addCommunicationRouteClient(cb, pid, device); + client = addCommunicationRouteClient(cb, uid, device, isPrivileged); if (client == null) { - Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: " - + pid + " and device: " + device); + Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: " + + uid + " and device: " + device); } } else { client = removeCommunicationRouteClient(cb, true); @@ -406,11 +412,11 @@ import java.util.concurrent.atomic.AtomicBoolean; boolean isBtScoRequested = isBluetoothScoRequested(); if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { - Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: " - + pid); + Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: " + + uid); // clean up or restore previous client selection if (prevClientDevice != null) { - addCommunicationRouteClient(cb, pid, prevClientDevice); + addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged); } else { removeCommunicationRouteClient(cb, true); } @@ -447,11 +453,12 @@ import java.util.concurrent.atomic.AtomicBoolean; @GuardedBy("mDeviceStateLock") private CommunicationRouteClient topCommunicationRouteClient() { for (CommunicationRouteClient crc : mCommunicationRouteClients) { - if (crc.getPid() == mAudioModeOwner.mPid) { + if (crc.getUid() == mAudioModeOwner.mUid) { return crc; } } - if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) { + if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0 + && mCommunicationRouteClients.get(0).isActive()) { return mCommunicationRouteClients.get(0); } return null; @@ -491,14 +498,48 @@ import java.util.concurrent.atomic.AtomicBoolean; }; /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) { + return isValidCommunicationDeviceType(device.getType()); + } + + private static boolean isValidCommunicationDeviceType(int deviceType) { for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { - if (device.getType() == type) { + if (deviceType == type) { return true; } } return false; } + /*package */ + void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) { + if (!isValidCommunicationDeviceType( + AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) { + return; + } + sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device); + } + + @GuardedBy("mDeviceStateLock") + void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString()); + } + for (CommunicationRouteClient crc : mCommunicationRouteClients) { + if (device.equals(crc.getDevice())) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: " + + crc.toString()); + } + // Cancelling the route for this client will remove it from the stack and update + // the communication route. + CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo( + crc.getBinder(), crc.getUid(), device, false, + BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval", + false, crc.isPrivileged()); + postSetCommunicationDeviceForClient(deviceInfo); + } + } + } /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() { ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>(); AudioDeviceInfo[] allDevices = @@ -1107,26 +1148,26 @@ import java.util.concurrent.atomic.AtomicBoolean; sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info); } - /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode, - @NonNull String eventSource) { + /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode, + boolean isPrivileged, @NonNull String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "startBluetoothScoForClient, pid: " + pid); + Log.v(TAG, "startBluetoothScoForClient, uid: " + uid); } postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( - cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), - true, scoAudioMode, eventSource, false)); + cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), + true, scoAudioMode, eventSource, false, isPrivileged)); } /*package*/ void stopBluetoothScoForClient( - IBinder cb, int pid, @NonNull String eventSource) { + IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid); + Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid); } postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( - cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), - false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); + cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), + false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged)); } /*package*/ int setPreferredDevicesForStrategySync(int strategy, @@ -1367,22 +1408,24 @@ import java.util.concurrent.atomic.AtomicBoolean; /*package*/ static final class CommunicationDeviceInfo { final @NonNull IBinder mCb; // Identifies the requesting client for death handler - final int mPid; // Requester process ID + final int mUid; // Requester UID final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset. final boolean mOn; // true if setting, false if resetting final int mScoAudioMode; // only used for SCO: requested audio mode + final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission final @NonNull String mEventSource; // caller identifier for logging boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent) boolean mStatus = false; // completion status only used if mWaitForStatus is true - CommunicationDeviceInfo(@NonNull IBinder cb, int pid, + CommunicationDeviceInfo(@NonNull IBinder cb, int uid, @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode, - @NonNull String eventSource, boolean waitForStatus) { + @NonNull String eventSource, boolean waitForStatus, boolean isPrivileged) { mCb = cb; - mPid = pid; + mUid = uid; mDevice = device; mOn = on; mScoAudioMode = scoAudioMode; + mIsPrivileged = isPrivileged; mEventSource = eventSource; mWaitForStatus = waitForStatus; } @@ -1401,16 +1444,17 @@ import java.util.concurrent.atomic.AtomicBoolean; } return mCb.equals(((CommunicationDeviceInfo) o).mCb) - && mPid == ((CommunicationDeviceInfo) o).mPid; + && mUid == ((CommunicationDeviceInfo) o).mUid; } @Override public String toString() { return "CommunicationDeviceInfo mCb=" + mCb.toString() - + " mPid=" + mPid + + " mUid=" + mUid + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]" + " mOn=" + mOn + " mScoAudioMode=" + mScoAudioMode + + " mIsPrivileged=" + mIsPrivileged + " mEventSource=" + mEventSource + " mWaitForStatus=" + mWaitForStatus + " mStatus=" + mStatus; @@ -1440,7 +1484,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, + /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, boolean connect, @Nullable BluetoothDevice btDevice) { synchronized (mDeviceStateLock) { return mDeviceInventory.handleDeviceConnection( @@ -1507,8 +1551,7 @@ import java.util.concurrent.atomic.AtomicBoolean; pw.println("\n" + prefix + "Communication route clients:"); mCommunicationRouteClients.forEach((cl) -> { - pw.println(" " + prefix + "pid: " + cl.getPid() + " device: " - + cl.getDevice() + " cb: " + cl.getBinder()); }); + pw.println(" " + prefix + cl.toString()); }); pw.println("\n" + prefix + "Computed Preferred communication device: " + preferredCommunicationDevice()); @@ -1850,6 +1893,15 @@ import java.util.concurrent.atomic.AtomicBoolean; final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; BtHelper.onNotifyPreferredAudioProfileApplied(btDevice); } break; + + case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: { + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj); + } + } + } break; + default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1926,6 +1978,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49; private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52; + private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { @@ -2101,13 +2154,20 @@ import java.util.concurrent.atomic.AtomicBoolean; private class CommunicationRouteClient implements IBinder.DeathRecipient { private final IBinder mCb; - private final int mPid; + private final int mUid; + private final boolean mIsPrivileged; private AudioDeviceAttributes mDevice; + private boolean mPlaybackActive; + private boolean mRecordingActive; - CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) { + CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device, + boolean isPrivileged) { mCb = cb; - mPid = pid; + mUid = uid; mDevice = device; + mIsPrivileged = isPrivileged; + mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid); + mRecordingActive = mAudioService.isRecordingActiveForUid(uid); } public boolean registerDeathRecipient() { @@ -2138,13 +2198,38 @@ import java.util.concurrent.atomic.AtomicBoolean; return mCb; } - int getPid() { - return mPid; + int getUid() { + return mUid; + } + + boolean isPrivileged() { + return mIsPrivileged; } AudioDeviceAttributes getDevice() { return mDevice; } + + public void setPlaybackActive(boolean active) { + mPlaybackActive = active; + } + + public void setRecordingActive(boolean active) { + mRecordingActive = active; + } + + public boolean isActive() { + return mIsPrivileged || mRecordingActive || mPlaybackActive; + } + + @Override + public String toString() { + return "[CommunicationRouteClient: mUid: " + mUid + + " mDevice: " + mDevice.toString() + + " mIsPrivileged: " + mIsPrivileged + + " mPlaybackActive: " + mPlaybackActive + + " mRecordingActive: " + mRecordingActive + "]"; + } } // @GuardedBy("mSetModeLock") @@ -2154,8 +2239,9 @@ import java.util.concurrent.atomic.AtomicBoolean; return; } Log.w(TAG, "Communication client died"); - setCommunicationRouteForClient(client.getBinder(), client.getPid(), null, - BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied"); + setCommunicationRouteForClient(client.getBinder(), client.getUid(), null, + BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(), + "onCommunicationRouteClientDied"); } /** @@ -2242,8 +2328,8 @@ import java.util.concurrent.atomic.AtomicBoolean; + crc + " eventSource: " + eventSource); } if (crc != null) { - setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(), - BtHelper.SCO_MODE_UNDEFINED, eventSource); + setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(), + BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource); } } @@ -2267,6 +2353,7 @@ import java.util.concurrent.atomic.AtomicBoolean; dispatchCommunicationDevice(); } + @GuardedBy("mDeviceStateLock") private CommunicationRouteClient removeCommunicationRouteClient( IBinder cb, boolean unregister) { for (CommunicationRouteClient cl : mCommunicationRouteClients) { @@ -2282,11 +2369,12 @@ import java.util.concurrent.atomic.AtomicBoolean; } @GuardedBy("mDeviceStateLock") - private CommunicationRouteClient addCommunicationRouteClient( - IBinder cb, int pid, AudioDeviceAttributes device) { + private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid, + AudioDeviceAttributes device, boolean isPrivileged) { // always insert new request at first position removeCommunicationRouteClient(cb, true); - CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device); + CommunicationRouteClient client = + new CommunicationRouteClient(cb, uid, device, isPrivileged); if (client.registerDeathRecipient()) { mCommunicationRouteClients.add(0, client); return client; @@ -2295,9 +2383,9 @@ import java.util.concurrent.atomic.AtomicBoolean; } @GuardedBy("mDeviceStateLock") - private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) { + private CommunicationRouteClient getCommunicationRouteClientForUid(int uid) { for (CommunicationRouteClient cl : mCommunicationRouteClients) { - if (cl.getPid() == pid) { + if (cl.getUid() == uid) { return cl; } } @@ -2330,6 +2418,45 @@ import java.util.concurrent.atomic.AtomicBoolean; return device; } + void updateCommunicationRouteClientsActivity( + List<AudioPlaybackConfiguration> playbackConfigs, + List<AudioRecordingConfiguration> recordConfigs) { + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + boolean updateCommunicationRoute = false; + for (CommunicationRouteClient crc : mCommunicationRouteClients) { + boolean wasActive = crc.isActive(); + if (playbackConfigs != null) { + crc.setPlaybackActive(false); + for (AudioPlaybackConfiguration config : playbackConfigs) { + if (config.getClientUid() == crc.getUid() + && config.isActive()) { + crc.setPlaybackActive(true); + break; + } + } + } + if (recordConfigs != null) { + crc.setRecordingActive(false); + for (AudioRecordingConfiguration config : recordConfigs) { + if (config.getClientUid() == crc.getUid() + && !config.isClientSilenced()) { + crc.setRecordingActive(true); + break; + } + } + } + if (wasActive != crc.isActive()) { + updateCommunicationRoute = true; + } + } + if (updateCommunicationRoute) { + postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity"); + } + } + } + } + @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { synchronized (mDeviceStateLock) { return mDeviceInventory.getDeviceSensorUuid(device); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 0c7f11f98809..b70e11ddcd91 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1245,8 +1245,9 @@ public class AudioDeviceInventory { * @param btDevice the corresponding Bluetooth device when relevant. * @return false if an error was reported by AudioSystem */ - /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, - boolean isForTesting, @Nullable BluetoothDevice btDevice) { + /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, + boolean connect, boolean isForTesting, + @Nullable BluetoothDevice btDevice) { int device = attributes.getInternalType(); String address = attributes.getAddress(); String deviceName = attributes.getName(); @@ -1297,6 +1298,7 @@ public class AudioDeviceInventory { AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); // always remove even if disconnection failed mConnectedDevices.remove(deviceKey); + mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes); status = true; } if (status) { @@ -1801,8 +1803,9 @@ public class AudioDeviceInventory { // device to remove was visible by APM, update APM mDeviceBroker.clearAvrcpAbsoluteVolumeSupported(); - final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec); if (res != AudioSystem.AUDIO_STATUS_OK) { @@ -1816,11 +1819,13 @@ public class AudioDeviceInventory { "A2DP device addr=" + address + " made unavailable")).printLog(TAG)); } mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + // Remove A2DP routes as well setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); mmi.record(); updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @GuardedBy("mDevicesLock") @@ -1855,12 +1860,14 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeA2dpSrcUnavailable(String address) { - mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); + mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @GuardedBy("mDevicesLock") @@ -1893,8 +1900,9 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeHearingAidDeviceUnavailable(String address) { - mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_HEARING_AID, address); + mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove( @@ -1906,6 +1914,7 @@ public class AudioDeviceInventory { .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) .record(); + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } /** @@ -2002,9 +2011,10 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeLeAudioDeviceUnavailableNow(String address, int device) { + AudioDeviceAttributes ada = null; if (device != AudioSystem.DEVICE_NONE) { - final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - device, address), + ada = new AudioDeviceAttributes(device, address); + final int res = AudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); @@ -2024,6 +2034,9 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); + if (ada != null) { + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); + } } @GuardedBy("mDevicesLock") diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 53ed38edffe4..b70b2b3041e0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4262,22 +4262,41 @@ public class AudioService extends IAudioService.Stub // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE // and request an audio mode update immediately. Upon any other change, queue the message // and request an audio mode update after a grace period. + updateAudioModeHandlers( + configs /* playbackConfigs */, null /* recordConfigs */); + mDeviceBroker.updateCommunicationRouteClientsActivity( + configs /* playbackConfigs */, null /* recordConfigs */); + } + + void updateAudioModeHandlers(List<AudioPlaybackConfiguration> playbackConfigs, + List<AudioRecordingConfiguration> recordConfigs) { synchronized (mDeviceBroker.mSetModeLock) { boolean updateAudioMode = false; int existingMsgPolicy = SENDMSG_QUEUE; int delay = CHECK_MODE_FOR_UID_PERIOD_MS; for (SetModeDeathHandler h : mSetModeDeathHandlers) { boolean wasActive = h.isActive(); - h.setPlaybackActive(false); - for (AudioPlaybackConfiguration config : configs) { - final int usage = config.getAudioAttributes().getUsage(); - if (config.getClientUid() == h.getUid() - && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION + if (playbackConfigs != null) { + h.setPlaybackActive(false); + for (AudioPlaybackConfiguration config : playbackConfigs) { + final int usage = config.getAudioAttributes().getUsage(); + if (config.getClientUid() == h.getUid() + && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) - && config.getPlayerState() - == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { - h.setPlaybackActive(true); - break; + && config.isActive()) { + h.setPlaybackActive(true); + break; + } + } + } + if (recordConfigs != null) { + h.setRecordingActive(false); + for (AudioRecordingConfiguration config : recordConfigs) { + if (config.getClientUid() == h.getUid() && !config.isClientSilenced() + && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) { + h.setRecordingActive(true); + break; + } } } if (wasActive != h.isActive()) { @@ -4315,38 +4334,10 @@ public class AudioService extends IAudioService.Stub // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE // and request an audio mode update immediately. Upon any other change, queue the message // and request an audio mode update after a grace period. - synchronized (mDeviceBroker.mSetModeLock) { - boolean updateAudioMode = false; - int existingMsgPolicy = SENDMSG_QUEUE; - int delay = CHECK_MODE_FOR_UID_PERIOD_MS; - for (SetModeDeathHandler h : mSetModeDeathHandlers) { - boolean wasActive = h.isActive(); - h.setRecordingActive(false); - for (AudioRecordingConfiguration config : configs) { - if (config.getClientUid() == h.getUid() - && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) { - h.setRecordingActive(true); - break; - } - } - if (wasActive != h.isActive()) { - updateAudioMode = true; - if (h.isActive() && h == getAudioModeOwnerHandler()) { - existingMsgPolicy = SENDMSG_REPLACE; - delay = 0; - } - } - } - if (updateAudioMode) { - sendMsg(mAudioHandler, - MSG_UPDATE_AUDIO_MODE, - existingMsgPolicy, - AudioSystem.MODE_CURRENT, - android.os.Process.myPid(), - mContext.getPackageName(), - delay); - } - } + updateAudioModeHandlers( + null /* playbackConfigs */, configs /* recordConfigs */); + mDeviceBroker.updateCommunicationRouteClientsActivity( + null /* playbackConfigs */, configs /* recordConfigs */); } private void dumpAudioMode(PrintWriter pw) { @@ -6299,10 +6290,12 @@ public class AudioService extends IAudioService.Stub ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) .record(); } - + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { - return mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource); + return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -6348,6 +6341,9 @@ public class AudioService extends IAudioService.Stub if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; // for logging only final int uid = Binder.getCallingUid(); @@ -6363,9 +6359,10 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.STATE, on ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF) .record(); + final long ident = Binder.clearCallingIdentity(); try { - mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource); + mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -6490,7 +6487,7 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.SCO_AUDIO_MODE, BtHelper.scoAudioModeToString(scoAudioMode)) .record(); - startBluetoothScoInt(cb, pid, scoAudioMode, eventSource); + startBluetoothScoInt(cb, uid, scoAudioMode, eventSource); } @@ -6513,10 +6510,10 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.SCO_AUDIO_MODE, BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL)) .record(); - startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); + startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); } - void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) { + void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) { MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH) .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt") .set(MediaMetrics.Property.SCO_AUDIO_MODE, @@ -6527,9 +6524,13 @@ public class AudioService extends IAudioService.Stub mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record(); return; } + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { - mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource); + mDeviceBroker.startBluetoothScoForClient( + cb, uid, scoAudioMode, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -6547,9 +6548,12 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("stopBluetoothSco()") .append(") from u/pid:").append(uid).append("/") .append(pid).toString(); + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { - mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource); + mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -9284,8 +9288,8 @@ public class AudioService extends IAudioService.Stub break; } boolean wasActive = h.isActive(); - h.setPlaybackActive(mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())); - h.setRecordingActive(mRecordMonitor.isRecordingActiveForUid(h.getUid())); + h.setPlaybackActive(isPlaybackActiveForUid(h.getUid())); + h.setRecordingActive(isRecordingActiveForUid(h.getUid())); if (wasActive != h.isActive()) { onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(), mContext.getPackageName(), false /*force*/); @@ -12378,6 +12382,16 @@ public class AudioService extends IAudioService.Stub } } + /* package */ + boolean isPlaybackActiveForUid(int uid) { + return mPlaybackMonitor.isPlaybackActiveForUid(uid); + } + + /* package */ + boolean isRecordingActiveForUid(int uid) { + return mRecordMonitor.isRecordingActiveForUid(uid); + } + //====================== // Audio device management //====================== diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 652ea5228571..4332fddf3f83 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -227,8 +227,8 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin synchronized (mRecordStates) { for (RecordingState state : mRecordStates) { // Note: isActiveConfiguration() == true => state.getConfig() != null - if (state.isActiveConfiguration() - && state.getConfig().getClientUid() == uid) { + if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid + && !state.getConfig().isClientSilenced()) { return true; } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index e85eee817d29..e3262cfbd30b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; @@ -280,15 +279,22 @@ public class Vpn { private static final int VPN_DEFAULT_SCORE = 101; /** - * The reset session timer for data stall. If a session has not successfully revalidated after - * the delay, the session will be torn down and restarted in an attempt to recover. Delay + * The recovery timer for data stall. If a session has not successfully revalidated after + * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay * counter is reset on successful validation only. * + * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE. + * System will perform session reset for the remaining timers. * <p>If retries have exceeded the length of this array, the last entry in the array will be * used as a repeating interval. */ - private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L}; - + // TODO: use ms instead to speed up the test. + private static final long[] DATA_STALL_RECOVERY_DELAYS_SEC = + {1L, 5L, 30L, 60L, 120L, 240L, 480L, 960L}; + /** + * Maximum attempts to perform MOBIKE when the network is bad. + */ + private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2; /** * The initial token value of IKE session. */ @@ -380,6 +386,7 @@ public class Vpn { private final INetworkManagementService mNms; private final INetd mNetd; @VisibleForTesting + @GuardedBy("this") protected VpnConfig mConfig; private final NetworkProvider mNetworkProvider; @VisibleForTesting @@ -392,7 +399,6 @@ public class Vpn { private final UserManager mUserManager; private final VpnProfileStore mVpnProfileStore; - protected boolean mDataStallSuspected = false; @VisibleForTesting VpnProfileStore getVpnProfileStore() { @@ -685,14 +691,14 @@ public class Vpn { } /** - * Get the length of time to wait before resetting the ike session when a data stall is - * suspected. + * Get the length of time to wait before perform data stall recovery when the validation + * result is bad. */ - public long getDataStallResetSessionSeconds(int count) { - if (count >= DATA_STALL_RESET_DELAYS_SEC.length) { - return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1]; + public long getValidationFailRecoverySeconds(int count) { + if (count >= DATA_STALL_RECOVERY_DELAYS_SEC.length) { + return DATA_STALL_RECOVERY_DELAYS_SEC[DATA_STALL_RECOVERY_DELAYS_SEC.length - 1]; } else { - return DATA_STALL_RESET_DELAYS_SEC[count]; + return DATA_STALL_RECOVERY_DELAYS_SEC[count]; } } @@ -1598,6 +1604,8 @@ public class Vpn { return network; } + // TODO : this is not synchronized(this) but reads from mConfig, which is dangerous + // This file makes an effort to avoid partly initializing mConfig, but this is still not great private LinkProperties makeLinkProperties() { // The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional // logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU @@ -1679,6 +1687,7 @@ public class Vpn { * registering a new NetworkAgent. This is not always possible if the new VPN configuration * has certain changes, in which case this method would just return {@code false}. */ + // TODO : this method is not synchronized(this) but reads from mConfig private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) { // NetworkAgentConfig cannot be updated without registering a new NetworkAgent. // Strictly speaking, bypassability is affected by lockdown and therefore it's possible @@ -2269,7 +2278,12 @@ public class Vpn { */ public synchronized VpnConfig getVpnConfig() { enforceControlPermission(); - return mConfig; + // Constructor of VpnConfig cannot take a null parameter. Return null directly if mConfig is + // null + if (mConfig == null) return null; + // mConfig is guarded by "this" and can be modified by another thread as soon as + // this method returns, so this method must return a copy. + return new VpnConfig(mConfig); } @Deprecated @@ -2315,6 +2329,7 @@ public class Vpn { } }; + @GuardedBy("this") private void cleanupVpnStateLocked() { mStatusIntent = null; resetNetworkCapabilities(); @@ -2837,9 +2852,7 @@ public class Vpn { } final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner; - mVpnRunner.exit(); - mVpnRunner = null; // LegacyVpn uses daemons that must be shut down before new ones are brought up. // The same limitation does not apply to Platform VPNs. @@ -3044,7 +3057,6 @@ public class Vpn { @Nullable private IkeSessionWrapper mSession; @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo; - @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback; // mMobikeEnabled can only be updated after IKE AUTH is finished. private boolean mMobikeEnabled = false; @@ -3055,7 +3067,7 @@ public class Vpn { * <p>This variable controls the retry delay, and is reset when the VPN pass network * validation. */ - private int mDataStallRetryCount = 0; + private int mValidationFailRetryCount = 0; /** * The number of attempts since the last successful connection. @@ -3084,6 +3096,7 @@ public class Vpn { } }; + // GuardedBy("Vpn.this") (annotation can't be applied to constructor) IkeV2VpnRunner( @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) { super(TAG); @@ -3136,15 +3149,6 @@ public class Vpn { mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback, new Handler(mLooper)); } - - // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on - // Network object. - final NetworkRequest diagRequest = new NetworkRequest.Builder() - .addTransportType(TRANSPORT_VPN) - .removeCapability(NET_CAPABILITY_NOT_VPN).build(); - mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback(); - mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback( - diagRequest, mExecutor, mDiagnosticsCallback); } private boolean isActiveNetwork(@Nullable Network network) { @@ -3710,11 +3714,14 @@ public class Vpn { } public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) { - final VpnTransportInfo info = new VpnTransportInfo( - getActiveVpnType(), - mConfig.session, - mConfig.allowBypass && !mLockdown, - areLongLivedTcpConnectionsExpensive(keepaliveDelaySec)); + final VpnTransportInfo info; + synchronized (Vpn.this) { + info = new VpnTransportInfo( + getActiveVpnType(), + mConfig.session, + mConfig.allowBypass && !mLockdown, + areLongLivedTcpConnectionsExpensive(keepaliveDelaySec)); + } final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo()); if (ncUpdateRequired) { mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities) @@ -3875,39 +3882,12 @@ public class Vpn { } } - class VpnConnectivityDiagnosticsCallback - extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { - // The callback runs in the executor thread. - @Override - public void onDataStallSuspected( - ConnectivityDiagnosticsManager.DataStallReport report) { - synchronized (Vpn.this) { - // Ignore stale runner. - if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return; - - // Handle the report only for current VPN network. If data stall is already - // reported, ignoring the other reports. It means that the stall is not - // recovered by MOBIKE and should be on the way to reset the ike session. - if (mNetworkAgent != null - && mNetworkAgent.getNetwork().equals(report.getNetwork()) - && !mDataStallSuspected) { - Log.d(TAG, "Data stall suspected"); - - // Trigger MOBIKE. - maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); - mDataStallSuspected = true; - } - } - } - } - public void onValidationStatus(int status) { mEventChanges.log("[Validation] validation status " + status); if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { - mDataStallSuspected = false; - mDataStallRetryCount = 0; + mValidationFailRetryCount = 0; if (mScheduledHandleDataStallFuture != null) { Log.d(TAG, "Recovered from stall. Cancel pending reset action."); mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */); @@ -3918,8 +3898,21 @@ public class Vpn { // Skip other invalid status if the scheduled recovery exists. if (mScheduledHandleDataStallFuture != null) return; + if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) { + Log.d(TAG, "Validation failed"); + + // Trigger MOBIKE to recover first. + mExecutor.schedule(() -> { + maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); + }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++), + TimeUnit.SECONDS); + return; + } + + // Data stall is not recovered by MOBIKE. Try to reset session to recover it. mScheduledHandleDataStallFuture = mExecutor.schedule(() -> { - if (mDataStallSuspected) { + // Only perform the recovery when the network is still bad. + if (mValidationFailRetryCount > 0) { Log.d(TAG, "Reset session to recover stalled network"); // This will reset old state if it exists. startIkeSession(mActiveNetwork); @@ -3928,7 +3921,9 @@ public class Vpn { // Reset mScheduledHandleDataStallFuture since it's already run on executor // thread. mScheduledHandleDataStallFuture = null; - }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS); + // TODO: compute the delay based on the last recovery timestamp + }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++), + TimeUnit.SECONDS); } } @@ -4220,7 +4215,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ private void disconnectVpnRunner() { - mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork); + mEventChanges.log("[VPNRunner] Disconnect runner, underlying net " + mActiveNetwork); mActiveNetwork = null; mUnderlyingNetworkCapabilities = null; mUnderlyingLinkProperties = null; @@ -4231,8 +4226,6 @@ public class Vpn { mCarrierConfigManager.unregisterCarrierConfigChangeListener( mCarrierConfigChangeListener); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); - mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback( - mDiagnosticsCallback); clearVpnNetworkPreference(mSessionKey); mExecutor.shutdown(); @@ -4293,6 +4286,7 @@ public class Vpn { } }; + // GuardedBy("Vpn.this") (annotation can't be applied to constructor) LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { super(TAG); if (racoon == null && mtpd == null) { @@ -4500,46 +4494,46 @@ public class Vpn { } // Set the interface and the addresses in the config. - mConfig.interfaze = parameters[0].trim(); + synchronized (Vpn.this) { + mConfig.interfaze = parameters[0].trim(); - mConfig.addLegacyAddresses(parameters[1]); - // Set the routes if they are not set in the config. - if (mConfig.routes == null || mConfig.routes.isEmpty()) { - mConfig.addLegacyRoutes(parameters[2]); - } + mConfig.addLegacyAddresses(parameters[1]); + // Set the routes if they are not set in the config. + if (mConfig.routes == null || mConfig.routes.isEmpty()) { + mConfig.addLegacyRoutes(parameters[2]); + } - // Set the DNS servers if they are not set in the config. - if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { - String dnsServers = parameters[3].trim(); - if (!dnsServers.isEmpty()) { - mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + // Set the DNS servers if they are not set in the config. + if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { + String dnsServers = parameters[3].trim(); + if (!dnsServers.isEmpty()) { + mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + } } - } - // Set the search domains if they are not set in the config. - if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { - String searchDomains = parameters[4].trim(); - if (!searchDomains.isEmpty()) { - mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); + // Set the search domains if they are not set in the config. + if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { + String searchDomains = parameters[4].trim(); + if (!searchDomains.isEmpty()) { + mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); + } } - } - // Add a throw route for the VPN server endpoint, if one was specified. - if (endpointAddress instanceof Inet4Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 32), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else if (endpointAddress instanceof Inet6Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 128), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else { - Log.e(TAG, "Unknown IP address family for VPN endpoint: " - + endpointAddress); - } + // Add a throw route for the VPN server endpoint, if one was specified. + if (endpointAddress instanceof Inet4Address) { + mConfig.routes.add(new RouteInfo( + new IpPrefix(endpointAddress, 32), null /*gateway*/, + null /*iface*/, RTN_THROW)); + } else if (endpointAddress instanceof Inet6Address) { + mConfig.routes.add(new RouteInfo( + new IpPrefix(endpointAddress, 128), null /*gateway*/, + null /*iface*/, RTN_THROW)); + } else { + Log.e(TAG, "Unknown IP address family for VPN endpoint: " + + endpointAddress); + } - // Here is the last step and it must be done synchronously. - synchronized (Vpn.this) { + // Here is the last step and it must be done synchronously. // Set the start time mConfig.startTime = SystemClock.elapsedRealtime(); @@ -4773,25 +4767,26 @@ public class Vpn { try { // Build basic config - mConfig = new VpnConfig(); + final VpnConfig config = new VpnConfig(); if (VpnConfig.LEGACY_VPN.equals(packageName)) { - mConfig.legacy = true; - mConfig.session = profile.name; - mConfig.user = profile.key; + config.legacy = true; + config.session = profile.name; + config.user = profile.key; // TODO: Add support for configuring meteredness via Settings. Until then, use a // safe default. - mConfig.isMetered = true; + config.isMetered = true; } else { - mConfig.user = packageName; - mConfig.isMetered = profile.isMetered; + config.user = packageName; + config.isMetered = profile.isMetered; } - mConfig.startTime = SystemClock.elapsedRealtime(); - mConfig.proxyInfo = profile.proxy; - mConfig.requiresInternetValidation = profile.requiresInternetValidation; - mConfig.excludeLocalRoutes = profile.excludeLocalRoutes; - mConfig.allowBypass = profile.isBypassable; - mConfig.disallowedApplications = getAppExclusionList(mPackage); + config.startTime = SystemClock.elapsedRealtime(); + config.proxyInfo = profile.proxy; + config.requiresInternetValidation = profile.requiresInternetValidation; + config.excludeLocalRoutes = profile.excludeLocalRoutes; + config.allowBypass = profile.isBypassable; + config.disallowedApplications = getAppExclusionList(mPackage); + mConfig = config; switch (profile.type) { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: @@ -4805,6 +4800,7 @@ public class Vpn { mVpnRunner.start(); break; default: + mConfig = null; updateState(DetailedState.FAILED, "Invalid platform VPN type"); Log.d(TAG, "Unknown VPN profile type: " + profile.type); break; @@ -5216,7 +5212,7 @@ public class Vpn { pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled")); pw.println("Profile: " + runner.mProfile); pw.println("Token: " + runner.mCurrentToken); - if (mDataStallSuspected) pw.println("Data stall suspected"); + pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount); if (runner.mScheduledHandleDataStallFuture != null) { pw.println("Reset session scheduled"); } diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 47cde1517450..5b11cfe7ff06 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -18,29 +18,42 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; import android.os.IBinder; +import android.provider.DeviceConfigInterface; + +import com.android.server.display.feature.DeviceConfigParameterProvider; import java.io.PrintWriter; import java.util.function.BooleanSupplier; class BrightnessRangeController { - private static final boolean NBM_FEATURE_FLAG = false; - private final HighBrightnessModeController mHbmController; private final NormalBrightnessModeController mNormalBrightnessModeController = new NormalBrightnessModeController(); private final Runnable mModeChangeCallback; + private final boolean mUseNbmController; + BrightnessRangeController(HighBrightnessModeController hbmController, Runnable modeChangeCallback) { + this(hbmController, modeChangeCallback, + new DeviceConfigParameterProvider(DeviceConfigInterface.REAL)); + } + + BrightnessRangeController(HighBrightnessModeController hbmController, + Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) { mHbmController = hbmController; mModeChangeCallback = modeChangeCallback; + mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled(); } - void dump(PrintWriter pw) { + pw.println("BrightnessRangeController:"); + pw.println(" mUseNormalBrightnessController=" + mUseNbmController); mHbmController.dump(pw); + mNormalBrightnessModeController.dump(pw); + } void onAmbientLuxChange(float ambientLux) { @@ -90,7 +103,7 @@ class BrightnessRangeController { float getCurrentBrightnessMax() { - if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode() + if (mUseNbmController && mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) { return Math.min(mHbmController.getCurrentBrightnessMax(), mNormalBrightnessModeController.getCurrentBrightnessMax()); @@ -111,7 +124,7 @@ class BrightnessRangeController { } private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) { - if (NBM_FEATURE_FLAG) { + if (mUseNbmController) { boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean(); hbmChangesFunc.run(); // if nbm transition changed - trigger callback diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index cfdcd636904b..c421ec04d6f5 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -22,7 +22,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessInfo; -import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IThermalEventListener; @@ -38,6 +37,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; import java.io.PrintWriter; import java.util.ArrayList; @@ -63,7 +63,7 @@ class BrightnessThrottler { private final Runnable mThrottlingChangeCallback; private final SkinThermalStatusObserver mSkinThermalStatusObserver; private final DeviceConfigListener mDeviceConfigListener; - private final DeviceConfigInterface mDeviceConfig; + private final DeviceConfigParameterProvider mConfigParameterProvider; private int mThrottlingStatus; @@ -118,7 +118,7 @@ class BrightnessThrottler { mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); mUniqueDisplayId = uniqueDisplayId; - mDeviceConfig = injector.getDeviceConfig(); + mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigListener = new DeviceConfigListener(); mThermalBrightnessThrottlingDataId = throttlingDataId; mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap; @@ -145,7 +145,7 @@ class BrightnessThrottler { void stop() { mSkinThermalStatusObserver.stopObserving(); - mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener); + mConfigParameterProvider.removeOnPropertiesChangedListener(mDeviceConfigListener); // We're asked to stop throttling, so reset brightness restrictions. mBrightnessCap = PowerManager.BRIGHTNESS_MAX; mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; @@ -248,12 +248,6 @@ class BrightnessThrottler { mSkinThermalStatusObserver.dump(pw); } - private String getThermalBrightnessThrottlingDataString() { - return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, - /* defaultValue= */ null); - } - // The brightness throttling data id may or may not be specified in the string that is passed // in, if there is none specified, we assume it is for the default case. Each string passed in // here must be for one display and one throttling id. @@ -318,7 +312,8 @@ class BrightnessThrottler { private void loadThermalBrightnessThrottlingDataFromDeviceConfig() { HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData = new HashMap<>(1); - mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString(); + mThermalBrightnessThrottlingDataString = + mConfigParameterProvider.getBrightnessThrottlingData(); boolean validConfig = true; mThermalBrightnessThrottlingDataOverride.clear(); if (mThermalBrightnessThrottlingDataString != null) { @@ -390,8 +385,7 @@ class BrightnessThrottler { public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler); public void startListening() { - mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - mExecutor, this); + mConfigParameterProvider.addOnPropertiesChangedListener(mExecutor, this); } @Override diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 858800a82151..dbe15b6e2da8 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -24,7 +24,6 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.hardware.display.DisplayManager.EventsMask; -import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; @@ -42,7 +41,6 @@ import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.ROOT_UID; -import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER; import android.Manifest; import android.annotation.NonNull; @@ -116,7 +114,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; @@ -152,6 +150,7 @@ import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayDeviceConfig.SensorData; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.display.utils.SensorUtils; @@ -506,6 +505,8 @@ public final class DisplayManagerService extends SystemService { private final BrightnessSynchronizer mBrightnessSynchronizer; + private final DeviceConfigParameterProvider mConfigParameterProvider; + /** * Applications use {@link android.view.Display#getRefreshRate} and * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate. @@ -558,6 +559,7 @@ public final class DisplayManagerService extends SystemService { mWideColorSpace = colorSpaces[1]; mOverlayProperties = SurfaceControl.getOverlaySupport(); mSystemReady = false; + mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); } public void setupSchedulerPolicies() { @@ -694,11 +696,11 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { mSafeMode = safeMode; mSystemReady = true; - mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled(); - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER, - BackgroundThread.getExecutor(), + mIsHdrOutputControlEnabled = + mConfigParameterProvider.isHdrOutputControlFeatureEnabled(); + mConfigParameterProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(), properties -> mIsHdrOutputControlEnabled = - isDeviceConfigHdrOutputControlEnabled()); + mConfigParameterProvider.isHdrOutputControlFeatureEnabled()); // Just in case the top inset changed before the system was ready. At this point, any // relevant configuration should be in place. recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)); @@ -729,12 +731,6 @@ public final class DisplayManagerService extends SystemService { mContext.registerReceiver(mIdleModeReceiver, filter); } - private boolean isDeviceConfigHdrOutputControlEnabled() { - return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER, - HDR_OUTPUT_CONTROL_FLAG, - true); - } - @VisibleForTesting Handler getDisplayHandler() { return mHandler; @@ -3158,8 +3154,7 @@ public final class DisplayManagerService extends SystemService { + "display: " + display.getDisplayIdLocked()); return null; } - if (DeviceConfig.getBoolean("display_manager", - "use_newly_structured_display_power_controller", true)) { + if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) { displayPowerController = new DisplayPowerController2( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 7701bc6271ae..89d865e5ae39 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -496,7 +496,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private void loadDisplayDeviceConfig() { // Load display device config final Context context = getOverlayContext(); - mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId, + mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId, mIsFirstDisplay); // Load brightness HWC quirk @@ -1336,6 +1336,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { public SurfaceControlProxy getSurfaceControlProxy() { return new SurfaceControlProxy(); } + + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay); + } } public interface DisplayEventListener { diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java index dbabc2441224..135ebd8f4fbf 100644 --- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java +++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java @@ -21,6 +21,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType; +import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @@ -60,6 +61,14 @@ class NormalBrightnessModeController { return recalculateMaxBrightness(); } + void dump(PrintWriter pw) { + pw.println("NormalBrightnessModeController:"); + pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled); + pw.println(" mAmbientLux=" + mAmbientLux); + pw.println(" mMaxBrightness=" + mMaxBrightness); + pw.println(" mMaxBrightnessLimits=" + mMaxBrightnessLimits); + } + private boolean recalculateMaxBrightness() { float foundAmbientBoundary = Float.MAX_VALUE; float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java new file mode 100644 index 000000000000..feebdf1b9799 --- /dev/null +++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.feature; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.display.DisplayManager; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; +import android.util.Slog; + +import java.util.concurrent.Executor; + +/** + * Helper class to access all DeviceConfig features for display_manager namespace + * + **/ +public class DeviceConfigParameterProvider { + + private static final String TAG = "DisplayFeatureProvider"; + + private final DeviceConfigInterface mDeviceConfig; + + public DeviceConfigParameterProvider(DeviceConfigInterface deviceConfig) { + mDeviceConfig = deviceConfig; + } + + // feature: revamping_display_power_controller_feature + // parameter: use_newly_structured_display_power_controller + public boolean isNewPowerControllerFeatureEnabled() { + return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true); + } + + // feature: hdr_output_control + // parameter: enable_hdr_output_control + public boolean isHdrOutputControlFeatureEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.HDR_OUTPUT_CONTROL_FLAG, true); + } + + // feature: flexible_brightness_range_feature + // parameter: normal_brightness_mode_controller_enabled + public boolean isNormalBrightnessControllerFeatureEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false); + } + + // feature: smooth_display_feature + // parameter: peak_refresh_rate_default + public float getPeakRefreshRateDefault() { + return mDeviceConfig.getFloat(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1); + } + + // Test parameters + // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90 + + // allows to customize brightness throttling data + public String getBrightnessThrottlingData() { + return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, null); + } + + public int getRefreshRateInHbmSunlight() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, -1); + } + + public int getRefreshRateInHbmHdr() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, -1); + } + + + public int getRefreshRateInHighZone() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, -1); + } + + public int getRefreshRateInLowZone() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getHighAmbientBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getHighDisplayBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getLowDisplayBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getLowAmbientBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); + } + + /** add property change listener to DeviceConfig */ + public void addOnPropertiesChangedListener(Executor executor, + DeviceConfig.OnPropertiesChangedListener listener) { + mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + executor, listener); + } + + /** remove property change listener from DeviceConfig */ + public void removeOnPropertiesChangedListener( + DeviceConfig.OnPropertiesChangedListener listener) { + mDeviceConfig.removeOnPropertiesChangedListener(listener); + } + + @Nullable + private int[] getIntArrayProperty(String prop) { + String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop, + null); + + if (strArray != null) { + return parseIntArray(strArray); + } + return null; + } + + @Nullable + private int[] parseIntArray(@NonNull String strArray) { + String[] items = strArray.split(","); + int[] array = new int[items.length]; + + try { + for (int i = 0; i < array.length; i++) { + array[i] = Integer.parseInt(items[i]); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e); + array = null; + } + + return array; + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 18895788e4ec..11e35ce09c28 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -20,6 +20,7 @@ import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLU import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; import static android.os.PowerManager.BRIGHTNESS_INVALID; +import android.annotation.IntegerRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; @@ -68,6 +69,7 @@ import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.utils.AmbientFilter; import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.SensorUtils; @@ -84,6 +86,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.function.IntSupplier; /** * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically @@ -117,7 +120,7 @@ public class DisplayModeDirector { private final SensorObserver mSensorObserver; private final HbmObserver mHbmObserver; private final SkinThermalStatusObserver mSkinThermalStatusObserver; - private final DeviceConfigInterface mDeviceConfig; + private final DeviceConfigParameterProvider mConfigParameterProvider; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @GuardedBy("mLock") @@ -157,7 +160,7 @@ public class DisplayModeDirector { mSupportedModesByDisplay = new SparseArray<>(); mDefaultModeByDisplay = new SparseArray<>(); mAppRequestObserver = new AppRequestObserver(); - mDeviceConfig = injector.getDeviceConfig(); + mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); mSettingsObserver = new SettingsObserver(context, handler); mBrightnessObserver = new BrightnessObserver(context, handler, injector); @@ -681,9 +684,9 @@ public class DisplayModeDirector { synchronized (mLock) { mDefaultDisplayDeviceConfig = displayDeviceConfig; mSettingsObserver.setRefreshRates(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); + /* attemptReadFromFeatureParams= */ true); mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); + /* attemptReadFromFeatureParams= */ true); mBrightnessObserver.reloadLightSensor(displayDeviceConfig); mHbmObserver.setupHdrRefreshRates(displayDeviceConfig); } @@ -1087,7 +1090,7 @@ public class DisplayModeDirector { // reading from the DeviceConfig is an intensive IO operation and having it in the // startup phase where we thrive to keep the latency very low has significant impact. setRefreshRates(/* displayDeviceConfig= */ null, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } /** @@ -1095,8 +1098,8 @@ public class DisplayModeDirector { * if missing from DisplayDeviceConfig, and finally fallback to config.xml. */ public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig); + boolean attemptReadFromFeatureParams) { + setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams); mDefaultRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultRefreshRate) @@ -1113,9 +1116,9 @@ public class DisplayModeDirector { cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, this); - Float deviceConfigDefaultPeakRefresh = - mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); - if (deviceConfigDefaultPeakRefresh != null) { + float deviceConfigDefaultPeakRefresh = + mConfigParameterProvider.getPeakRefreshRateDefault(); + if (deviceConfigDefaultPeakRefresh != -1) { mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh; } @@ -1137,7 +1140,7 @@ public class DisplayModeDirector { synchronized (mLock) { if (defaultPeakRefreshRate == null) { setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); updateRefreshRateSettingLocked(); } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { mDefaultPeakRefreshRate = defaultPeakRefreshRate; @@ -1171,18 +1174,17 @@ public class DisplayModeDirector { } private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - Float defaultPeakRefreshRate = null; + boolean attemptReadFromFeatureParams) { + float defaultPeakRefreshRate = -1; - if (attemptLoadingFromDeviceConfig) { + if (attemptReadFromFeatureParams) { try { - defaultPeakRefreshRate = - mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); + defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); } catch (Exception exception) { // Do nothing } } - if (defaultPeakRefreshRate == null) { + if (defaultPeakRefreshRate == -1) { defaultPeakRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultPeakRefreshRate) @@ -1528,7 +1530,7 @@ public class DisplayModeDirector { mHandler = handler; mInjector = injector; updateBlockingZoneThresholds(/* displayDeviceConfig= */ null, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); mRefreshRateInHighZone = context.getResources().getInteger( R.integer.config_fixedRefreshRateInHighZone); } @@ -1537,10 +1539,10 @@ public class DisplayModeDirector { * This is used to update the blocking zone thresholds from the DeviceConfig, which * if missing from DisplayDeviceConfig, and finally fallback to config.xml. */ - public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig); - loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig); + public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, + boolean attemptReadFromFeatureParams) { + loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); + loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); } @VisibleForTesting @@ -1579,20 +1581,20 @@ public class DisplayModeDirector { return mRefreshRateInLowZone; } - private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); - loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); + private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, + boolean attemptReadFromFeatureParams) { + loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams); + loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams); mLowDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), R.array.config_brightnessThresholdsOfPeakRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); mLowAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), R.array.config_ambientThresholdsOfPeakRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) { throw new RuntimeException("display low brightness threshold array and ambient " + "brightness threshold array have different length: " @@ -1604,55 +1606,55 @@ public class DisplayModeDirector { } private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - int refreshRateInLowZone = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInZone) - : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); - if (attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { + int refreshRateInLowZone = -1; + if (attemptReadFromFeatureParams) { try { - refreshRateInLowZone = mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, - refreshRateInLowZone); + refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); } catch (Exception exception) { // Do nothing } } + if (refreshRateInLowZone == -1) { + refreshRateInLowZone = (displayDeviceConfig == null) + ? mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInZone) + : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); + } mRefreshRateInLowZone = refreshRateInLowZone; } private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - int refreshRateInHighZone = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig - .getDefaultHighBlockingZoneRefreshRate(); - if (attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { + int refreshRateInHighZone = -1; + if (attemptReadFromFeatureParams) { try { - refreshRateInHighZone = mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, - refreshRateInHighZone); + refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); } catch (Exception exception) { // Do nothing } } + if (refreshRateInHighZone == -1) { + refreshRateInHighZone = (displayDeviceConfig == null) + ? mContext.getResources().getInteger( + R.integer.config_fixedRefreshRateInHighZone) + : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(); + } mRefreshRateInHighZone = refreshRateInHighZone; } private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { mHighDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(), () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); mHighAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); if (mHighDisplayBrightnessThresholds.length != mHighAmbientBrightnessThresholds.length) { throw new RuntimeException("display high brightness threshold array and ambient " @@ -1668,10 +1670,10 @@ public class DisplayModeDirector { Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable, Callable<int[]> loadFromDisplayDeviceConfigCallable, int brightnessThresholdOfFixedRefreshRateKey, - DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) { + DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams) { int[] brightnessThresholds = null; - if (attemptLoadingFromDeviceConfig) { + if (attemptReadFromFeatureParams) { try { brightnessThresholds = loadFromDeviceConfigDisplaySettingsCallable.call(); @@ -1715,9 +1717,9 @@ public class DisplayModeDirector { // DeviceConfig is accessible after system ready. int[] lowDisplayBrightnessThresholds = - mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(); + mConfigParameterProvider.getLowDisplayBrightnessThresholds(); int[] lowAmbientBrightnessThresholds = - mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(); + mConfigParameterProvider.getLowAmbientBrightnessThresholds(); if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null && lowDisplayBrightnessThresholds.length @@ -1727,9 +1729,9 @@ public class DisplayModeDirector { } int[] highDisplayBrightnessThresholds = - mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(); + mConfigParameterProvider.getHighDisplayBrightnessThresholds(); int[] highAmbientBrightnessThresholds = - mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(); + mConfigParameterProvider.getHighAmbientBrightnessThresholds(); if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null && highDisplayBrightnessThresholds.length @@ -1738,14 +1740,12 @@ public class DisplayModeDirector { mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds; } - final int refreshRateInLowZone = mDeviceConfigDisplaySettings - .getRefreshRateInLowZone(); + final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); if (refreshRateInLowZone != -1) { mRefreshRateInLowZone = refreshRateInLowZone; } - final int refreshRateInHighZone = mDeviceConfigDisplaySettings - .getRefreshRateInHighZone(); + final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); if (refreshRateInHighZone != -1) { mRefreshRateInHighZone = refreshRateInHighZone; } @@ -1799,15 +1799,15 @@ public class DisplayModeDirector { displayDeviceConfig = mDefaultDisplayDeviceConfig; } mLowDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), R.array.config_brightnessThresholdsOfPeakRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); mLowAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), R.array.config_ambientThresholdsOfPeakRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); } restartObserver(); } @@ -1822,7 +1822,7 @@ public class DisplayModeDirector { // from there. synchronized (mLock) { loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } restartObserver(); } else if (refreshRate != mRefreshRateInLowZone) { @@ -1843,15 +1843,15 @@ public class DisplayModeDirector { displayDeviceConfig = mDefaultDisplayDeviceConfig; } mHighDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); mHighAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); } restartObserver(); } @@ -1866,7 +1866,7 @@ public class DisplayModeDirector { // from there. synchronized (mLock) { loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } restartObserver(); } else if (refreshRate != mRefreshRateInHighZone) { @@ -2675,113 +2675,55 @@ public class DisplayModeDirector { private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener { public void startListening() { - mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + mConfigParameterProvider.addOnPropertiesChangedListener( BackgroundThread.getExecutor(), this); } - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getLowDisplayBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getLowAmbientBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); - } - - public int getRefreshRateInLowZone() { - return mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1); - - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getHighDisplayBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS); - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getHighAmbientBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS); + private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { + return getRefreshRate( + () -> mConfigParameterProvider.getRefreshRateInHbmHdr(), + () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(), + R.integer.config_defaultRefreshRateInHbmHdr, + displayDeviceConfig + ); } - public int getRefreshRateInHighZone() { - return mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, - -1); + private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { + return getRefreshRate( + () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(), + () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(), + R.integer.config_defaultRefreshRateInHbmSunlight, + displayDeviceConfig + ); } - public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { - int refreshRate = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInHbmHdr) - : displayDeviceConfig.getDefaultRefreshRateInHbmHdr(); + private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig, + @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) { + int refreshRate = -1; try { - refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, - refreshRate); - } catch (NullPointerException e) { + refreshRate = fromConfigPram.getAsInt(); + } catch (NullPointerException npe) { // Do Nothing } - return refreshRate; - } - - public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { - int refreshRate = - (displayDeviceConfig == null) ? mContext.getResources() - .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight) - : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(); - try { - refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, - refreshRate); - } catch (NullPointerException e) { - // Do Nothing + if (refreshRate == -1) { + refreshRate = (displayDeviceConfig == null) + ? mContext.getResources().getInteger(configKey) + : fromDisplayDeviceConfig.getAsInt(); } return refreshRate; } - /* - * Return null if no such property - */ - public Float getDefaultPeakRefreshRate() { - float defaultPeakRefreshRate = mDeviceConfig.getFloat( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1); - - if (defaultPeakRefreshRate == -1) { - return null; - } - return defaultPeakRefreshRate; - } - @Override public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { - Float defaultPeakRefreshRate = getDefaultPeakRefreshRate(); + float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED, - defaultPeakRefreshRate).sendToTarget(); + defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget(); - int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds(); - int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds(); - final int refreshRateInLowZone = getRefreshRateInLowZone(); + int[] lowDisplayBrightnessThresholds = + mConfigParameterProvider.getLowDisplayBrightnessThresholds(); + int[] lowAmbientBrightnessThresholds = + mConfigParameterProvider.getLowAmbientBrightnessThresholds(); + final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds)) @@ -2790,9 +2732,11 @@ public class DisplayModeDirector { mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0).sendToTarget(); - int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds(); - int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds(); - final int refreshRateInHighZone = getRefreshRateInHighZone(); + int[] highDisplayBrightnessThresholds = + mConfigParameterProvider.getHighDisplayBrightnessThresholds(); + int[] highAmbientBrightnessThresholds = + mConfigParameterProvider.getHighAmbientBrightnessThresholds(); + final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds)) @@ -2814,33 +2758,6 @@ public class DisplayModeDirector { .sendToTarget(); } } - - private int[] getIntArrayProperty(String prop) { - String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop, - null); - - if (strArray != null) { - return parseIntArray(strArray); - } - - return null; - } - - private int[] parseIntArray(@NonNull String strArray) { - String[] items = strArray.split(","); - int[] array = new int[items.length]; - - try { - for (int i = 0; i < array.length; i++) { - array[i] = Integer.parseInt(items[i]); - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e); - array = null; - } - - return array; - } } interface Injector { diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 6d70d21e3b84..633bf73120e1 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -16,11 +16,11 @@ package com.android.server.dreams; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; +import android.app.IAppTask; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -214,6 +214,27 @@ final class DreamController { } /** + * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller + * can stop the dream task when necessary. + */ + void setDreamAppTask(Binder dreamToken, IAppTask appTask) { + if (mCurrentDream == null || mCurrentDream.mToken != dreamToken + || mCurrentDream.mAppTask != null) { + Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = " + + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken + + ". Ending this dream activity."); + try { + appTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop illegal dream activity."); + } + return; + } + + mCurrentDream.mAppTask = appTask; + } + + /** * Stops dreaming. * * The current dream, if any, and any unstopped previous dreams are stopped. The device stops @@ -303,8 +324,14 @@ final class DreamController { mSentStartBroadcast = false; } - mActivityTaskManager.removeRootTasksWithActivityTypes( - new int[] {ACTIVITY_TYPE_DREAM}); + if (mCurrentDream != null && mCurrentDream.mAppTask != null) { + // Finish the dream task in case it hasn't finished by itself already. + try { + mCurrentDream.mAppTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop dream activity."); + } + } mListener.onDreamStopped(dream.mToken); } @@ -364,6 +391,7 @@ final class DreamController { public final boolean mIsPreviewMode; public final boolean mCanDoze; public final int mUserId; + public IAppTask mAppTask; public PowerManager.WakeLock mWakeLock; public boolean mBound; diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 68cf59f0ae1f..d88fe8a6c201 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -27,6 +27,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.IAppTask; import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -37,6 +38,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.hardware.display.AmbientDisplayConfiguration; @@ -116,6 +118,7 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final PackageManagerInternal mPmInternal; private final UserManager mUserManager; private final UiEventLogger mUiEventLogger; private final DreamUiEventLogger mDreamUiEventLogger; @@ -216,6 +219,7 @@ public final class DreamManagerService extends SystemService { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = getLocalService(PowerManagerInternal.class); mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); + mPmInternal = getLocalService(PackageManagerInternal.class); mUserManager = context.getSystemService(UserManager.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); @@ -1064,6 +1068,64 @@ public final class DreamManagerService extends SystemService { Binder.restoreCallingIdentity(ident); } } + + @Override // Binder call + public void startDreamActivity(@NonNull Intent intent) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + // We post here, because startDreamActivity and setDreamAppTask have to run + // synchronously and DreamController#setDreamAppTask has to run on mHandler. + mHandler.post(() -> { + final Binder dreamToken; + final String dreamPackageName; + synchronized (mLock) { + if (mCurrentDream == null) { + Slog.e(TAG, "Attempt to start DreamActivity, but the device is not " + + "dreaming. Aborting without starting the DreamActivity."); + return; + } + dreamToken = mCurrentDream.token; + dreamPackageName = mCurrentDream.name.getPackageName(); + } + + if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(), + callingUid)) { + Slog.e(TAG, "The dream activity can be started only when the device is dreaming" + + " and only by the active dream package."); + return; + } + + final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid, + callingPid); + if (appTask == null) { + Slog.e(TAG, "Could not start dream activity."); + stopDreamInternal(true, "DreamActivity not started"); + return; + } + mController.setDreamAppTask(dreamToken, appTask); + }); + } + + boolean canLaunchDreamActivity(String dreamPackageName, String packageName, + int callingUid) { + if (dreamPackageName == null || packageName == null) { + Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= " + + dreamPackageName + ", packageName=" + packageName); + return false; + } + if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) { + Slog.e(TAG, "Cannot launch dream activity because package=" + + packageName + " does not match callingUid=" + callingUid); + return false; + } + if (packageName.equals(dreamPackageName)) { + return true; + } + Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName + + " does not match " + dreamPackageName); + return false; + } + } private final class LocalService extends DreamManagerInternal { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 6a177e03102f..3a8f9d5c35e6 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -2800,6 +2800,11 @@ public class InputManagerService extends IInputManager.Stub void notifyConfigurationChanged(); /** + * This callback is invoked when the pointer location changes. + */ + void notifyPointerLocationChanged(boolean pointerLocationEnabled); + + /** * This callback is invoked when the camera lens cover switch changes state. * @param whenNanos the time when the change occurred * @param lensCovered true is the lens is covered @@ -3381,6 +3386,10 @@ public class InputManagerService extends IInputManager.Stub } } + void updatePointerLocationEnabled(boolean enabled) { + mWindowManagerCallbacks.notifyPointerLocationChanged(enabled); + } + void updateFocusEventDebugViewEnabled(boolean enabled) { FocusEventDebugView view; synchronized (mFocusEventDebugViewLock) { diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 42591f40b22e..a608b4f48082 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -67,6 +67,8 @@ class InputSettingsObserver extends ContentObserver { (reason) -> updateTouchpadRightClickZoneEnabled()), Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), (reason) -> updateShowTouches()), + Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION), + (reason) -> updatePointerLocation()), Map.entry( Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON), (reason) -> updateAccessibilityLargePointer()), @@ -149,6 +151,11 @@ class InputSettingsObserver extends ContentObserver { mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false)); } + private void updatePointerLocation() { + mService.updatePointerLocationEnabled( + getBoolean(Settings.System.POINTER_LOCATION, false)); + } + private void updateShowKeyPresses() { mService.updateFocusEventDebugViewEnabled( getBoolean(Settings.System.SHOW_KEY_PRESSES, false)); diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index 6a0550b60c15..aa99dab8f007 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -291,6 +291,10 @@ final class HandwritingModeController { reset(false /* reinitializing */); } + void setInkWindowInitializer(Runnable inkWindowInitializer) { + mInkWindowInitRunnable = inkWindowInitializer; + } + private void reset(boolean reinitializing) { if (mHandwritingEventReceiver != null) { mHandwritingEventReceiver.dispose(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 1ab83f7c5fe5..02ee96a04b1f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2472,6 +2472,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); + if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { + mHwController.setInkWindowInitializer(new InkWindowInitializer()); + } return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, session.mSession, accessibilityInputMethodSessions, (session.mChannel != null ? session.mChannel.dup() : null), diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 65e34e682724..398e470d9fda 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -147,7 +147,7 @@ public final class MediaProjectionManagerService extends SystemService mInjector = injector; mClock = injector.createClock(); mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>(); - mCallbackDelegate = new CallbackDelegate(); + mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper()); mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManager = mContext.getPackageManager(); @@ -182,6 +182,11 @@ public final class MediaProjectionManagerService extends SystemService Clock createClock() { return SystemClock::uptimeMillis; } + + /** Creates the {@link Looper} to be used when notifying callbacks. */ + Looper createCallbackLooper() { + return Looper.getMainLooper(); + } } @Override @@ -268,7 +273,8 @@ public final class MediaProjectionManagerService extends SystemService dispatchStop(projection); } - private void addCallback(final IMediaProjectionWatcherCallback callback) { + @VisibleForTesting + void addCallback(final IMediaProjectionWatcherCallback callback) { IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { @@ -315,6 +321,12 @@ public final class MediaProjectionManagerService extends SystemService mCallbackDelegate.dispatchStop(projection); } + private void dispatchSessionSet( + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallbackDelegate.dispatchSession(projectionInfo, session); + } + /** * Returns {@code true} when updating the current mirroring session on WM succeeded, and * {@code false} otherwise. @@ -335,6 +347,7 @@ public final class MediaProjectionManagerService extends SystemService if (mProjectionGrant != null) { // Cache the session details. mProjectionGrant.mSession = incomingSession; + dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession); } return true; } @@ -1155,8 +1168,8 @@ public final class MediaProjectionManagerService extends SystemService private Handler mHandler; private final Object mLock = new Object(); - public CallbackDelegate() { - mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/); + CallbackDelegate(Looper callbackLooper) { + mHandler = new Handler(callbackLooper, null, true /*async*/); mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>(); mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>(); } @@ -1219,6 +1232,16 @@ public final class MediaProjectionManagerService extends SystemService } } + public void dispatchSession( + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + synchronized (mLock) { + for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { + mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session)); + } + } + } + public void dispatchResize(MediaProjection projection, int width, int height) { if (projection == null) { Slog.e(TAG, @@ -1335,6 +1358,29 @@ public final class MediaProjectionManagerService extends SystemService } } + private static final class WatcherSessionCallback implements Runnable { + private final IMediaProjectionWatcherCallback mCallback; + private final MediaProjectionInfo mProjectionInfo; + private final ContentRecordingSession mSession; + + WatcherSessionCallback( + @NonNull IMediaProjectionWatcherCallback callback, + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallback = callback; + mProjectionInfo = projectionInfo; + mSession = session; + } + + @Override + public void run() { + try { + mCallback.onRecordingSessionSet(mProjectionInfo, mSession); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify content recording session changed", e); + } + } + } private static String typeToString(int type) { switch (type) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 68f49fd0481f..e0e6410a965d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -9508,14 +9508,19 @@ public class NotificationManagerService extends SystemService { * Determine whether the userId applies to the notification in question, either because * they match exactly, or one of them is USER_ALL (which is treated as a wildcard). */ - private static boolean notificationMatchesUserId(NotificationRecord r, int userId) { - return + private static boolean notificationMatchesUserId(NotificationRecord r, int userId, + boolean isAutogroupSummary) { + if (isAutogroupSummary) { + return r.getUserId() == userId; + } else { + return // looking for USER_ALL notifications? match everything - userId == UserHandle.USER_ALL - // a notification sent to USER_ALL matches any query - || r.getUserId() == UserHandle.USER_ALL - // an exact user match - || r.getUserId() == userId; + userId == UserHandle.USER_ALL + // a notification sent to USER_ALL matches any query + || r.getUserId() == UserHandle.USER_ALL + // an exact user match + || r.getUserId() == userId; + } } /** @@ -9524,7 +9529,7 @@ public class NotificationManagerService extends SystemService { * because it matches one of the users profiles. */ private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) { - return notificationMatchesUserId(r, userId) + return notificationMatchesUserId(r, userId, false) || mUserProfiles.isCurrentProfile(r.getUserId()); } @@ -9593,7 +9598,7 @@ public class NotificationManagerService extends SystemService { if (!notificationMatchesCurrentProfiles(r, userId)) { continue; } - } else if (!notificationMatchesUserId(r, userId)) { + } else if (!notificationMatchesUserId(r, userId, false)) { continue; } // Don't remove notifications to all, if there's no package name specified @@ -9831,7 +9836,7 @@ public class NotificationManagerService extends SystemService { final int len = list.size(); for (int i = 0; i < len; i++) { NotificationRecord r = list.get(i); - if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey) + if (notificationMatchesUserId(r, userId, false) && r.getGroupKey().equals(groupKey) && r.getSbn().getPackageName().equals(pkg)) { records.add(r); } @@ -9873,8 +9878,8 @@ public class NotificationManagerService extends SystemService { final int len = list.size(); for (int i = 0; i < len; i++) { NotificationRecord r = list.get(i); - if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id && - TextUtils.equals(r.getSbn().getTag(), tag) + if (notificationMatchesUserId(r, userId, (r.getFlags() & GroupHelper.BASE_FLAGS) != 0) + && r.getSbn().getId() == id && TextUtils.equals(r.getSbn().getTag(), tag) && r.getSbn().getPackageName().equals(pkg)) { return r; } @@ -9888,8 +9893,8 @@ public class NotificationManagerService extends SystemService { final int len = list.size(); for (int i = 0; i < len; i++) { NotificationRecord r = list.get(i); - if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id && - TextUtils.equals(r.getSbn().getTag(), tag) + if (notificationMatchesUserId(r, userId, false) && r.getSbn().getId() == id + && TextUtils.equals(r.getSbn().getTag(), tag) && r.getSbn().getPackageName().equals(pkg)) { matching.add(r); } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 5f52c16d319c..c1d5af589e8c 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -610,7 +610,8 @@ final class DeletePackageHelper { PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /*splashScreenTheme*/, - 0 /*firstInstallTime*/); + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); } mPm.mSettings.writeKernelMappingLPr(ps); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index db47306ad58e..2fc22bf79d91 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -30,6 +30,7 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; @@ -5239,6 +5240,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override + @PackageManager.UserMinAspectRatio + public int getUserMinAspectRatio(@NonNull String packageName, int userId) { + final Computer snapshot = snapshotComputer(); + final int callingUid = Binder.getCallingUid(); + snapshot.enforceCrossUserPermission( + callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "getUserMinAspectRatio"); + final PackageStateInternal packageState = snapshot + .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); + return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET + : packageState.getUserStateOrDefault(userId).getMinAspectRatio(); + } + + @Override public Bundle getSuspendedPackageAppExtras(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); final Computer snapshot = snapshot(); @@ -6201,6 +6216,32 @@ public class PackageManagerService implements PackageSender, TestUtilityService return true; } + @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES) + @Override + public void setUserMinAspectRatio(@NonNull String packageName, int userId, + @PackageManager.UserMinAspectRatio int aspectRatio) { + setUserMinAspectRatio_enforcePermission(); + final int callingUid = Binder.getCallingUid(); + final Computer snapshot = snapshotComputer(); + snapshot.enforceCrossUserPermission(callingUid, userId, + false /* requireFullPermission */, false /* checkShell */, + "setUserMinAspectRatio"); + enforceOwnerRights(snapshot, packageName, callingUid); + + final PackageStateInternal packageState = snapshot + .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); + if (packageState == null) { + return; + } + + if (packageState.getUserStateOrDefault(userId).getMinAspectRatio() == aspectRatio) { + return; + } + + commitPackageStateMutation(null, packageName, state -> + state.userState(userId).setMinAspectRatio(aspectRatio)); + } + @Override @SuppressWarnings("GuardedBy") public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 24118203bb56..3e9ccac2993a 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -875,7 +875,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, int installReason, int uninstallReason, String harmfulAppWarning, String splashScreenTheme, - long firstInstallTime) { + long firstInstallTime, int aspectRatio) { modifyUserState(userId) .setSuspendParams(suspendParams) .setCeDataInode(ceDataInode) @@ -894,7 +894,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal .setVirtualPreload(virtualPreload) .setHarmfulAppWarning(harmfulAppWarning) .setSplashScreenTheme(splashScreenTheme) - .setFirstInstallTimeMillis(firstInstallTime); + .setFirstInstallTimeMillis(firstInstallTime) + .setMinAspectRatio(aspectRatio); onChanged(); } @@ -912,7 +913,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal ? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(), otherState.getInstallReason(), otherState.getUninstallReason(), otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(), - otherState.getFirstInstallTimeMillis()); + otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio()); } WatchedArraySet<String> getEnabledComponents(int userId) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index aaf13ebeff2a..532ae718c030 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -351,6 +351,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload"; private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning"; private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme"; + private static final String ATTR_MIN_ASPECT_RATIO = "min-aspect-ratio"; private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint"; @@ -1122,7 +1123,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /*splashscreenTheme*/, - 0 /*firstInstallTime*/ + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET ); } } @@ -1776,7 +1778,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /* splashScreenTheme*/, - 0 /*firstInstallTime*/ + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET ); } return; @@ -1871,6 +1874,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ATTR_SPLASH_SCREEN_THEME); final long firstInstallTime = parser.getAttributeLongHex(null, ATTR_FIRST_INSTALL_TIME, 0); + final int minAspectRatio = parser.getAttributeInt(null, + ATTR_MIN_ASPECT_RATIO, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; @@ -1947,7 +1953,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile enabledCaller, enabledComponents, disabledComponents, installReason, uninstallReason, harmfulAppWarning, splashScreenTheme, firstInstallTime != 0 ? firstInstallTime : - origFirstInstallTimes.getOrDefault(name, 0L)); + origFirstInstallTimes.getOrDefault(name, 0L), + minAspectRatio); mDomainVerificationManager.setLegacyUserState(name, userId, verifState); } else if (tagName.equals("preferred-activities")) { @@ -2242,6 +2249,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME, ustate.getSplashScreenTheme()); } + if (ustate.getMinAspectRatio() + != PackageManager.USER_MIN_ASPECT_RATIO_UNSET) { + serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO, + ustate.getMinAspectRatio()); + } if (ustate.isSuspended()) { for (int i = 0; i < ustate.getSuspendParams().size(); i++) { final String suspendingPackage = ustate.getSuspendParams().keyAt(i); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 5b3514c01f9f..710e0b72ecfb 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -15,6 +15,7 @@ */ package com.android.server.pm; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; import android.Manifest.permission; @@ -24,6 +25,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IUidObserver; import android.app.IUriGrantsManager; @@ -4407,8 +4409,11 @@ public class ShortcutService extends IShortcutService.Stub { return; } try { + ActivityOptions options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_DENIED); intentSender.sendIntent(mContext, /* code= */ 0, extras, - /* onFinished=*/ null, /* handler= */ null); + /* onFinished=*/ null, /* handler= */ null, null, options.toBundle()); } catch (SendIntentException e) { Slog.w(TAG, "sendIntent failed().", e); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7e88e13e1788..f8bd3289a0b3 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1037,7 +1037,7 @@ public class UserManagerService extends IUserManager.Stub { final UserData userData = mUsers.valueAt(i); final int userId = userData.info.id; if (userId != currentUser && userData.info.isFull() && !userData.info.partial - && !mRemovingUserIds.get(userId)) { + && userData.info.isEnabled() && !mRemovingUserIds.get(userId)) { final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis; if (userEnteredTime > latestEnteredTime) { latestEnteredTime = userEnteredTime; @@ -2927,14 +2927,14 @@ public class UserManagerService extends IUserManager.Stub { Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId) != newBaseRestrictions); - if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) { + if (mBaseUserRestrictions.updateRestrictions(userId, new Bundle(newBaseRestrictions))) { scheduleWriteUser(userId); } } final Bundle effective = computeEffectiveUserRestrictionsLR(userId); - mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective); + mCachedEffectiveUserRestrictions.updateRestrictions(userId, new Bundle(effective)); // Apply the new restrictions. if (DBG) { @@ -5589,8 +5589,14 @@ public class UserManagerService extends IUserManager.Stub { } } - @GuardedBy("mUsersLock") @VisibleForTesting + void addRemovingUserId(@UserIdInt int userId) { + synchronized (mUsersLock) { + addRemovingUserIdLocked(userId); + } + } + + @GuardedBy("mUsersLock") void addRemovingUserIdLocked(@UserIdInt int userId) { // We remember deleted user IDs to prevent them from being // reused during the current boot; they can still be reused diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java index 91a25db3710e..3d056e89ba78 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java @@ -377,6 +377,8 @@ public class PackageStateImpl implements PackageState { private final int mUninstallReason; @Nullable private final String mSplashScreenTheme; + @PackageManager.UserMinAspectRatio + private final int mMinAspectRatio; private final long mFirstInstallTimeMillis; private UserStateImpl(@NonNull PackageUserState userState) { @@ -392,6 +394,7 @@ public class PackageStateImpl implements PackageState { mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths(); mUninstallReason = userState.getUninstallReason(); mSplashScreenTheme = userState.getSplashScreenTheme(); + mMinAspectRatio = userState.getMinAspectRatio(); setBoolean(Booleans.HIDDEN, userState.isHidden()); setBoolean(Booleans.INSTALLED, userState.isInstalled()); setBoolean(Booleans.INSTANT_APP, userState.isInstantApp()); @@ -543,6 +546,11 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return mMinAspectRatio; + } + + @DataClass.Generated.Member public long getFirstInstallTimeMillis() { return mFirstInstallTimeMillis; } @@ -554,10 +562,10 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1671671043891L, + time = 1687938966108L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") + inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 2048d651e11e..f75d214c5128 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -217,4 +217,12 @@ public interface PackageUserState { */ @Nullable String getSplashScreenTheme(); + + /** + * @return the min aspect ratio setting of the package which by default is unset + * unless it has been set by the user + * @hide + */ + @PackageManager.UserMinAspectRatio + int getMinAspectRatio(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 73fb67298aec..1fb12a8ce218 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -136,6 +136,11 @@ class PackageUserStateDefault implements PackageUserStateInternal { } @Override + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + } + + @Override public long getFirstInstallTimeMillis() { return 0; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index e8e2d4179326..d911ac1cfcd8 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -83,6 +83,9 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt @Nullable private String mSplashScreenTheme; + @PackageManager.UserMinAspectRatio + private int mMinAspectRatio = PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + /** * Suspending package to suspend params */ @@ -146,6 +149,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt mHarmfulAppWarning = other.mHarmfulAppWarning; mLastDisableAppCaller = other.mLastDisableAppCaller; mSplashScreenTheme = other.mSplashScreenTheme; + mMinAspectRatio = other.mMinAspectRatio; mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot(); mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null ? null : other.mComponentLabelIconOverrideMap.snapshot(); @@ -508,6 +512,19 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } /** + * Sets user min aspect ratio override value + * @see PackageManager.UserMinAspectRatio + */ + public @NonNull PackageUserStateImpl setMinAspectRatio( + @PackageManager.UserMinAspectRatio int value) { + mMinAspectRatio = value; + com.android.internal.util.AnnotationValidations.validate( + PackageManager.UserMinAspectRatio.class, null, mMinAspectRatio); + onChanged(); + return this; + } + + /** * Suspending package to suspend params */ public @NonNull PackageUserStateImpl setSuspendParams( @@ -679,6 +696,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return mSplashScreenTheme; } + @DataClass.Generated.Member + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return mMinAspectRatio; + } + /** * Suspending package to suspend params */ @@ -766,6 +788,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt && Objects.equals(mOverlayPaths, that.mOverlayPaths) && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths) && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme) + && mMinAspectRatio == that.mMinAspectRatio && Objects.equals(mSuspendParams, that.mSuspendParams) && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap) && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis @@ -798,6 +821,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt _hash = 31 * _hash + Objects.hashCode(mOverlayPaths); _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths); _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme); + _hash = 31 * _hash + mMinAspectRatio; _hash = 31 * _hash + Objects.hashCode(mSuspendParams); _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap); _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis); @@ -807,10 +831,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated( - time = 1686952839807L, + time = 1687938397579L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java index 8125b0f662aa..8430cf7a0d11 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java @@ -439,6 +439,16 @@ public class PackageStateMutator { } return null; } + + @NonNull + @Override + public PackageUserStateWrite setMinAspectRatio( + @PackageManager.UserMinAspectRatio int aspectRatio) { + if (mUserState != null) { + mUserState.setMinAspectRatio(aspectRatio); + } + return this; + } } } } diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java index 11d6d97d3920..0c6c6723b79b 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; +import com.android.server.pm.pkg.PackageUserStateImpl; import com.android.server.pm.pkg.SuspendParams; public interface PackageUserStateWrite { @@ -68,4 +69,8 @@ public interface PackageUserStateWrite { @NonNull PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName, @Nullable String nonLocalizedLabel, @Nullable Integer icon); + + /** @see PackageUserStateImpl#setMinAspectRatio(int) */ + @NonNull + PackageUserStateWrite setMinAspectRatio(@PackageManager.UserMinAspectRatio int aspectRatio); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a792b9c0dbdb..279a48084252 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -91,6 +91,7 @@ import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; @@ -632,6 +633,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { SettingsObserver mSettingsObserver; ModifierShortcutManager mModifierShortcutManager; + /** Currently fully consumed key codes per device */ + private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>(); PowerManager.WakeLock mBroadcastWakeLock; PowerManager.WakeLock mPowerKeyWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; @@ -708,8 +711,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { finishKeyguardDrawn(); break; case MSG_WINDOW_MANAGER_DRAWN_COMPLETE: - if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete"); - finishWindowsDrawn(msg.arg1); + final int displayId = msg.arg1; + if (DEBUG_WAKEUP) Slog.w(TAG, "All windows drawn on display " + displayId); + Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + finishWindowsDrawn(displayId); break; case MSG_HIDE_BOOT_MESSAGE: handleHideBootMessage(); @@ -1814,7 +1820,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDisplayId = displayId; } - int handleHomeButton(IBinder focusedToken, KeyEvent event) { + boolean handleHomeButton(IBinder focusedToken, KeyEvent event) { final boolean keyguardOn = keyguardOn(); final int repeatCount = event.getRepeatCount(); final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; @@ -1835,12 +1841,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHomePressed = false; if (mHomeConsumed) { mHomeConsumed = false; - return -1; + return true; } if (canceled) { Log.i(TAG, "Ignoring HOME; event canceled."); - return -1; + return true; } // Delay handling home if a double-tap is possible. @@ -1852,13 +1858,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHomeDoubleTapPending = true; mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, ViewConfiguration.getDoubleTapTimeout()); - return -1; + return true; } } // Post to main thread to avoid blocking input pipeline. mHandler.post(() -> handleShortPressOnHome(mDisplayId)); - return -1; + return true; } final KeyInterceptionInfo info = @@ -1870,12 +1876,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { || (info.layoutParamsType == TYPE_NOTIFICATION_SHADE && isKeyguardShowing())) { // the "app" is keyguard, so give it the key - return 0; + return false; } for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) { if (info.layoutParamsType == t) { // don't do anything, but also don't pass it to the app - return -1; + return true; } } } @@ -1900,7 +1906,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.getEventTime())); } } - return -1; + return true; } private void handleDoubleTapOnHome() { @@ -2946,24 +2952,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags) { - final boolean keyguardOn = keyguardOn(); final int keyCode = event.getKeyCode(); - final int repeatCount = event.getRepeatCount(); - final int metaState = event.getMetaState(); final int flags = event.getFlags(); - final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; - final boolean canceled = event.isCanceled(); - final int displayId = event.getDisplayId(); - final long key_consumed = -1; - final long key_not_consumed = 0; + final long keyConsumed = -1; + final long keyNotConsumed = 0; + final int deviceId = event.getDeviceId(); if (DEBUG_INPUT) { - Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" - + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled); + Log.d(TAG, + "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction() + + " repeatCount=" + event.getRepeatCount() + " keyguardOn=" + + keyguardOn() + " canceled=" + event.isCanceled()); } if (mKeyCombinationManager.isKeyConsumed(event)) { - return key_consumed; + return keyConsumed; } if ((flags & KeyEvent.FLAG_FALLBACK) == 0) { @@ -2974,8 +2977,54 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // Cancel any pending meta actions if we see any other keys being pressed between the down - // of the meta key and its corresponding up. + Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId); + if (consumedKeys == null) { + consumedKeys = new HashSet<>(); + mConsumedKeysForDevice.put(deviceId, consumedKeys); + } + + if (interceptSystemKeysAndShortcuts(focusedToken, event) + && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + consumedKeys.add(keyCode); + return keyConsumed; + } + + boolean needToConsumeKey = consumedKeys.contains(keyCode); + if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) { + consumedKeys.remove(keyCode); + if (consumedKeys.isEmpty()) { + mConsumedKeysForDevice.remove(deviceId); + } + } + + return needToConsumeKey ? keyConsumed : keyNotConsumed; + } + + // You can only start consuming the key gesture if ACTION_DOWN and repeat count + // is 0. If you start intercepting the key halfway, then key will not be consumed + // and will be sent to apps for processing too. + // e.g. If a certain combination is only handled on ACTION_UP (i.e. + // interceptShortcut() returns true only for ACTION_UP), then since we already + // sent the ACTION_DOWN events to the application, we MUST also send the + // ACTION_UP to the application. + // So, to ensure that your intercept logic works properly, and we don't send any + // conflicting events to application, make sure to consume the event on + // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential + // to maintain event parity and to not have incomplete key gestures. + @SuppressLint("MissingPermission") + private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) { + final boolean keyguardOn = keyguardOn(); + final int keyCode = event.getKeyCode(); + final int repeatCount = event.getRepeatCount(); + final int metaState = event.getMetaState(); + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final boolean canceled = event.isCanceled(); + final int displayId = event.getDisplayId(); + final int deviceId = event.getDeviceId(); + final boolean firstDown = down && repeatCount == 0; + + // Cancel any pending meta actions if we see any other keys being pressed between the + // down of the meta key and its corresponding up. if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) { mPendingMetaAction = false; } @@ -2989,50 +3038,49 @@ public class PhoneWindowManager implements WindowManagerPolicy { dismissKeyboardShortcutsMenu(); mPendingMetaAction = false; mPendingCapsLockToggle = false; - return key_consumed; + return true; } } - switch(keyCode) { + switch (keyCode) { case KeyEvent.KEYCODE_HOME: - logKeyboardSystemsEvent(event, FrameworkStatsLog - .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME); + logKeyboardSystemsEvent(event, + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME); return handleHomeShortcuts(displayId, focusedToken, event); case KeyEvent.KEYCODE_MENU: // Hijack modified menu keys for debugging features final int chordBug = KeyEvent.META_SHIFT_ON; - if (down && repeatCount == 0) { - if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { - Intent intent = new Intent(Intent.ACTION_BUG_REPORT); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, - null, null, null, 0, null, null); - return key_consumed; - } + if (mEnableShiftMenuBugReports && firstDown + && (metaState & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, + null, null, null, 0, null, null); + return true; } break; case KeyEvent.KEYCODE_RECENT_APPS: - if (down && repeatCount == 0) { + if (firstDown) { showRecentApps(false /* triggeredFromAltTab */); - logKeyboardSystemsEvent(event, FrameworkStatsLog - .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS); + logKeyboardSystemsEvent(event, + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS); } - return key_consumed; + return true; case KeyEvent.KEYCODE_APP_SWITCH: if (!keyguardOn) { - if (down && repeatCount == 0) { + if (firstDown) { preloadRecentApps(); } else if (!down) { toggleRecentApps(); } } - return key_consumed; + return true; case KeyEvent.KEYCODE_A: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, - event.getDeviceId(), - event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN); - return key_consumed; + deviceId, event.getEventTime(), + AssistUtils.INVOCATION_TYPE_UNKNOWN); + return true; } break; case KeyEvent.KEYCODE_H: @@ -3042,73 +3090,73 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_I: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { showSystemSettings(); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_L: - if (down && event.isMetaPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed()) { lockNow(null /* options */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_N: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { if (event.isCtrlPressed()) { sendSystemKeyToStatusBarAsync(event); } else { toggleNotificationPanel(); } - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_S: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_T: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { toggleTaskbar(); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_DPAD_UP: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { statusbar.goToFullscreenFromSplit(); + return true; } - return key_consumed; } break; case KeyEvent.KEYCODE_DPAD_LEFT: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { enterStageSplitFromRunningApp(true /* leftOrTop */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { enterStageSplitFromRunningApp(false /* leftOrTop */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_SLASH: - if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) { + if (firstDown && event.isMetaPressed() && !keyguardOn) { toggleKeyboardShortcutsMenu(event.getDeviceId()); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_ASSIST: Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_VOICE_ASSIST: Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in" + " interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_VIDEO_APP_1: case KeyEvent.KEYCODE_VIDEO_APP_2: case KeyEvent.KEYCODE_VIDEO_APP_3: @@ -3126,7 +3174,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_DEMO_APP_3: case KeyEvent.KEYCODE_DEMO_APP_4: Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_BRIGHTNESS_UP: case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: if (down) { @@ -3166,20 +3214,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), UserHandle.CURRENT_OR_SELF); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: if (down) { mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId()); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP: if (down) { mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId()); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE: // TODO: Add logic - return key_consumed; + return true; case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: @@ -3187,7 +3235,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // On TVs or when the configuration is enabled, volume keys never // go to the foreground app. dispatchDirectAudioEvent(event); - return key_consumed; + return true; } // If the device is in VR mode and keys are "internal" (e.g. on the side of the @@ -3196,26 +3244,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) { final InputDevice d = event.getDevice(); if (d != null && !d.isExternal()) { - return key_consumed; + return true; } } break; case KeyEvent.KEYCODE_TAB: - if (down && event.isMetaPressed()) { - if (!keyguardOn && isUserSetupComplete()) { + if (firstDown && !keyguardOn && isUserSetupComplete()) { + if (event.isMetaPressed()) { showRecentApps(false); - return key_consumed; - } - } else if (down && repeatCount == 0) { - // Display task switcher for ALT-TAB. - if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) { + return true; + } else if (mRecentAppsHeldModifiers == 0) { final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; if (KeyEvent.metaStateHasModifiers( shiftlessModifiers, KeyEvent.META_ALT_ON)) { mRecentAppsHeldModifiers = shiftlessModifiers; showRecentApps(true); - return key_consumed; + return true; } } } @@ -3227,18 +3272,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); msg.sendToTarget(); } - return key_consumed; + return true; case KeyEvent.KEYCODE_NOTIFICATION: if (!down) { toggleNotificationPanel(); } - return key_consumed; + return true; case KeyEvent.KEYCODE_SEARCH: - if (down && repeatCount == 0 && !keyguardOn()) { - switch(mSearchKeyBehavior) { + if (firstDown && !keyguardOn) { + switch (mSearchKeyBehavior) { case SEARCH_BEHAVIOR_TARGET_ACTIVITY: { launchTargetSearchActivity(); - return key_consumed; + return true; } case SEARCH_BEHAVIOR_DEFAULT_SEARCH: default: @@ -3247,21 +3292,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_LANGUAGE_SWITCH: - if (down && repeatCount == 0) { + if (firstDown) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(event, direction); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_SPACE: // Handle keyboard layout switching. (META + SPACE) - if ((metaState & KeyEvent.META_META_MASK) == 0) { - return key_not_consumed; - } - if (down && repeatCount == 0) { + if (firstDown && event.isMetaPressed()) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(event, direction); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_META_LEFT: @@ -3286,7 +3328,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingMetaAction = false; } } - return key_consumed; + return true; case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: if (down) { @@ -3302,14 +3344,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (metaState & mRecentAppsHeldModifiers) == 0) { mRecentAppsHeldModifiers = 0; hideRecentApps(true, false); - return key_consumed; + return true; } // Toggle Caps Lock on META-ALT. if (mPendingCapsLockToggle) { mInputManagerInternal.toggleCapsLock(event.getDeviceId()); mPendingCapsLockToggle = false; - return key_consumed; + return true; } } break; @@ -3319,24 +3361,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in" + " interceptKeyBeforeQueueing"); - return key_consumed; + return true; } - if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { - return key_consumed; + return true; } // Reserve all the META modifier combos for system behavior - if ((metaState & KeyEvent.META_META_ON) != 0) { - return key_consumed; - } - - // Let the application handle the key. - return key_not_consumed; + return (metaState & KeyEvent.META_META_ON) != 0; } - private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { + private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { // First we always handle the home key here, so applications // can never break it, although if keyguard is on, we do let // it handle it, because that gives us the correct 5 second @@ -3618,19 +3654,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { - if (mKeyguardDelegate != null && waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { + if (mKeyguardDelegate != null) { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; - } else { - setKeyguardOccludedLw(occluded); } } @Override public int applyKeyguardOcclusionChange() { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded=" - + mPendingKeyguardOccluded); + + mPendingKeyguardOccluded + " changed=" + mKeyguardOccludedChanged); // TODO(b/276433230): Explicitly save before/after for occlude state in each // Transition so we don't need to update SysUI every time. @@ -5046,15 +5080,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // ... eventually calls finishWindowsDrawn which will finalize our screen turn on // as well as enabling the orientation change logic/sensor. Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display"); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - INVALID_DISPLAY, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, INVALID_DISPLAY /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, INVALID_DISPLAY, 0), + WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); } // Called on the DisplayManager's DisplayPowerController thread. @@ -5134,15 +5163,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenOnListeners.put(displayId, screenOnListener); Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - displayId, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, displayId); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, displayId, 0), + WAITING_FOR_DRAWN_TIMEOUT, displayId); } } diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 9c3b38ab51a3..b999bbb3dce2 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -314,7 +314,9 @@ public final class SingleKeyGestureDetector { if (eventTime < mLastDownTime + mActiveRule.getVeryLongPressTimeoutMs()) { mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); } else { - mHandledByLongPress = mActiveRule.supportVeryLongPress(); + // If long press or very long press (~3.5s) had been handled, we should skip the + // short press behavior. + mHandledByLongPress |= mActiveRule.supportVeryLongPress(); } } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 887f9461bdce..03a7bd3b68b3 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -169,7 +169,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * * @param occluded Whether Keyguard is currently occluded or not. */ - void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition); + void onKeyguardOccludedChangedLw(boolean occluded); /** * Commit any queued changes to keyguard occlude status that had been deferred during the diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 4a57592aa1ae..27329e20bc8d 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -14613,17 +14613,13 @@ public class BatteryStatsImpl extends BatteryStats { // Inform StatsLog of setBatteryState changes. private void reportChangesToStatsLog(final int status, final int plugType, final int level) { - if (!mHaveBatteryLevel) { - return; - } - - if (mBatteryStatus != status) { + if (!mHaveBatteryLevel || mBatteryStatus != status) { FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status); } - if (mBatteryPlugType != plugType) { + if (!mHaveBatteryLevel || mBatteryPlugType != plugType) { FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType); } - if (mBatteryLevel != level) { + if (!mHaveBatteryLevel || mBatteryLevel != level) { FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level); } } diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java index 09ba19508476..83605ae09e97 100644 --- a/services/core/java/com/android/server/utils/AlarmQueue.java +++ b/services/core/java/com/android/server/utils/AlarmQueue.java @@ -151,6 +151,10 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { @GuardedBy("mLock") @ElapsedRealtimeLong private long mTriggerTimeElapsed = NOT_SCHEDULED; + /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */ + @GuardedBy("mLock") + @ElapsedRealtimeLong + private long mLastFireTimeElapsed; /** * @param alarmTag The tag to use when scheduling the alarm with AlarmManager. @@ -284,7 +288,7 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */ @GuardedBy("mLock") private void setNextAlarmLocked() { - setNextAlarmLocked(mInjector.getElapsedRealtime()); + setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs); } /** @@ -334,6 +338,7 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { final ArraySet<K> expired = new ArraySet<>(); synchronized (mLock) { final long nowElapsed = mInjector.getElapsedRealtime(); + mLastFireTimeElapsed = nowElapsed; while (mAlarmPriorityQueue.size() > 0) { final Pair<K, Long> alarm = mAlarmPriorityQueue.peek(); if (alarm.second <= nowElapsed) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 9b2cdd72a338..ee7dc5007d97 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3113,7 +3113,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) { Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + " updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId) + && !isLockscreenLiveWallpaperEnabled()) { + which |= FLAG_LOCK; + } } wallpaper = getWallpaperSafeLocked(userId, which); @@ -3141,13 +3144,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void migrateStaticSystemToLockWallpaperLocked(int userId) { + private boolean migrateStaticSystemToLockWallpaperLocked(int userId) { WallpaperData sysWP = mWallpaperMap.get(userId); if (sysWP == null) { if (DEBUG) { Slog.i(TAG, "No system wallpaper? Not tracking for lock-only"); } - return; + return true; } // We know a-priori that there is no lock-only wallpaper currently @@ -3174,9 +3177,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub SELinux.restorecon(lockWP.getWallpaperFile()); mLastLockWallpaper = lockWP; } + return true; } catch (ErrnoException e) { - Slog.e(TAG, "Can't migrate system wallpaper: " + e.getMessage()); + // can happen when migrating default wallpaper (which is not stored in wallpaperFile) + Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage()); clearWallpaperBitmaps(lockWP); + return false; } } @@ -3388,7 +3394,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // therefore it's a shared system+lock image that we need to migrate. Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + "updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId)) { + which |= FLAG_LOCK; + } } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index f3001133338a..b3ae2ee30f22 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1280,45 +1280,53 @@ final class AccessibilityController { } void drawIfNeeded(SurfaceControl.Transaction t) { + // Drawing variables (alpha, dirty rect, and bounds) access is synchronized + // using WindowManagerGlobalLock. Grab copies of these values before + // drawing on the canvas so that drawing can be performed outside of the lock. + int alpha; + Rect drawingRect = null; + Region drawingBounds = null; synchronized (mService.mGlobalLock) { if (!mInvalidated) { return; } mInvalidated = false; - if (mAlpha > 0) { - Canvas canvas = null; - try { - // Empty dirty rectangle means unspecified. - if (mDirtyRect.isEmpty()) { - mBounds.getBounds(mDirtyRect); - } - mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); - canvas = mSurface.lockCanvas(mDirtyRect); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); - } - } catch (IllegalArgumentException iae) { - /* ignore */ - } catch (Surface.OutOfResourcesException oore) { - /* ignore */ - } - if (canvas == null) { - return; + + alpha = mAlpha; + if (alpha > 0) { + drawingBounds = new Region(mBounds); + // Empty dirty rectangle means unspecified. + if (mDirtyRect.isEmpty()) { + mBounds.getBounds(mDirtyRect); } + mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); + drawingRect = new Rect(mDirtyRect); if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Bounds: " + mBounds); + Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds); + Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect); } - canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); - mPaint.setAlpha(mAlpha); - Path path = mBounds.getBoundaryPath(); - canvas.drawPath(path, mPaint); - - mSurface.unlockCanvasAndPost(canvas); - t.show(mSurfaceControl); - } else { - t.hide(mSurfaceControl); } } + + // Draw without holding WindowManagerGlobalLock. + if (alpha > 0) { + Canvas canvas = null; + try { + canvas = mSurface.lockCanvas(drawingRect); + } catch (IllegalArgumentException | OutOfResourcesException e) { + /* ignore */ + } + if (canvas == null) { + return; + } + canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + mPaint.setAlpha(alpha); + canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint); + mSurface.unlockCanvasAndPost(canvas); + t.show(mSurfaceControl); + } else { + t.hide(mSurfaceControl); + } } void releaseSurface() { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 32f1f42aacb9..a2547fd437d1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.AppProtoEnums; import android.app.BackgroundStartPrivileges; import android.app.IActivityManager; +import android.app.IAppTask; import android.app.IApplicationThread; import android.app.ITaskStackListener; import android.app.ProfilerInfo; @@ -308,6 +309,12 @@ public abstract class ActivityTaskManagerInternal { public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent); /** + * Starts a dream activity in the DreamService's process. + */ + public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, + int callingPid); + + /** * Set a uid that is allowed to bypass stopped app switches, launching an app * whenever it wants. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 10ff3a3b2e9b..78da5def43d4 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1467,23 +1467,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return false; } - private void enforceCallerIsDream(String callerPackageName) { - final long origId = Binder.clearCallingIdentity(); - try { - if (!canLaunchDreamActivity(callerPackageName)) { - throw new SecurityException("The dream activity can be started only when the device" - + " is dreaming and only by the active dream package."); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override - public boolean startDreamActivity(@NonNull Intent intent) { - assertPackageMatchesCallingUid(intent.getPackage()); - enforceCallerIsDream(intent.getPackage()); - + private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid, + int callingPid) { final ActivityInfo a = new ActivityInfo(); a.theme = com.android.internal.R.style.Theme_Dream; a.exported = true; @@ -1501,7 +1486,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); synchronized (mGlobalLock) { - final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid()); + final WindowProcessController process = mProcessMap.getProcess(callingPid); a.packageName = process.mInfo.packageName; a.applicationInfo = process.mInfo; @@ -1509,26 +1494,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.uiOptions = process.mInfo.uiOptions; a.taskAffinity = "android:" + a.packageName + "/dream"; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final long origId = Binder.clearCallingIdentity(); - try { - getActivityStartController().obtainStarter(intent, "dream") - .setCallingUid(callingUid) - .setCallingPid(callingPid) - .setCallingPackage(intent.getPackage()) - .setActivityInfo(a) - .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) - // To start the dream from background, we need to start it from a persistent - // system process. Here we set the real calling uid to the system server uid - .setRealCallingUid(Binder.getCallingUid()) - .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) - .execute(); - return true; - } finally { - Binder.restoreCallingIdentity(origId); - } + final ActivityRecord[] outActivity = new ActivityRecord[1]; + getActivityStartController().obtainStarter(intent, "dream") + .setCallingUid(callingUid) + .setCallingPid(callingPid) + .setCallingPackage(intent.getPackage()) + .setActivityInfo(a) + .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) + .setOutActivity(outActivity) + // To start the dream from background, we need to start it from a persistent + // system process. Here we set the real calling uid to the system server uid + .setRealCallingUid(Binder.getCallingUid()) + .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) + .execute(); + + final ActivityRecord started = outActivity[0]; + final IAppTask appTask = started == null ? null : + new AppTaskImpl(this, started.getTask().mTaskId, callingUid); + return appTask; } } @@ -5926,6 +5910,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) { + return startDreamActivityInternal(intent, callingUid, callingPid); + } + + @Override public void setAllowAppSwitches(@NonNull String type, int uid, int userId) { if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) { return; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 12b5f5f774de..bfd2a10a8882 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3701,6 +3701,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInputMonitor.dump(pw, " "); pw.println(); mInsetsStateController.dump(prefix, pw); + mInsetsPolicy.dump(prefix, pw); mDwpcHelper.dump(prefix, pw); pw.println(); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index a02fd11ba832..2717a6a8ab04 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -327,8 +327,6 @@ public class DisplayPolicy { private WindowState mTopFullscreenOpaqueWindowState; private boolean mTopIsFullscreen; private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED; - private boolean mForceConsumeSystemBars; - private boolean mForceShowSystemBars; /** * Windows that provides gesture insets. If multiple windows provide gesture insets at the same @@ -1286,18 +1284,10 @@ public class DisplayPolicy { return ANIMATION_STYLEABLE; } - /** - * @return true if the system bars are forced to be consumed - */ + // TODO (b/277891341): Remove this and related usages. This has been replaced by + // InsetsSource#FLAG_FORCE_CONSUMING. public boolean areSystemBarsForcedConsumedLw() { - return mForceConsumeSystemBars; - } - - /** - * @return true if the system bars are forced to stay visible - */ - public boolean areSystemBarsForcedShownLw() { - return mForceShowSystemBars; + return false; } /** @@ -1694,7 +1684,8 @@ public class DisplayPolicy { * @return Whether the top fullscreen app hides the given type of system bar. */ boolean topAppHidesSystemBar(@InsetsType int type) { - if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) { + if (mTopFullscreenOpaqueWindowState == null + || getInsetsPolicy().areTypesForciblyShowing(type)) { return false; } return !mTopFullscreenOpaqueWindowState.isRequestedVisible(type); @@ -2371,14 +2362,7 @@ public class DisplayPolicy { final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); - // We need to force showing system bars when adjacent tasks or freeform roots visible. - mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; - // We need to force the consumption of the system bars if they are force shown or if they - // are controlled by a remote insets controller. - mForceConsumeSystemBars = mForceShowSystemBars - || getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win) - || getInsetsPolicy().forcesShowingNavigationBars(win); - mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); + getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible); final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars()); if (getStatusBar() != null) { diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 0a47fe09dbd9..20595eaa1e3c 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -128,6 +128,21 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } + /** Notifies that the pointer location configuration has changed. */ + @Override + public void notifyPointerLocationChanged(boolean pointerLocationEnabled) { + if (mService.mPointerLocationEnabled == pointerLocationEnabled) { + return; + } + + synchronized (mService.mGlobalLock) { + mService.mPointerLocationEnabled = pointerLocationEnabled; + mService.mRoot.forAllDisplayPolicies( + p -> p.setPointerLocationEnabled(mService.mPointerLocationEnabled) + ); + } + } + /** Notifies that the lid switch changed state. */ @Override public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 798dc85ec11b..835c92d0a30d 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -21,12 +21,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.view.InsetsController.ANIMATION_TYPE_HIDE; -import static android.view.InsetsController.ANIMATION_TYPE_SHOW; -import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; -import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsSource.ID_IME; -import static android.view.SyncRtSurfaceTransactionApplier.applyParams; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; @@ -39,16 +34,13 @@ import android.app.StatusBarManager; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.res.Resources; +import android.os.Handler; +import android.os.IBinder; import android.util.SparseArray; -import android.view.InsetsAnimationControlCallbacks; -import android.view.InsetsAnimationControlImpl; -import android.view.InsetsAnimationControlRunner; import android.view.InsetsController; import android.view.InsetsFrameProvider; import android.view.InsetsSource; -import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InternalInsetsAnimationController; import android.view.SurfaceControl; import android.view.SyncRtSurfaceTransactionApplier; import android.view.WindowInsets; @@ -56,14 +48,16 @@ import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation.Bounds; -import android.view.WindowInsetsAnimationControlListener; import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.DisplayThread; import com.android.server.statusbar.StatusBarManagerInternal; +import java.io.PrintWriter; +import java.util.List; + /** * Policy that implements who gets control over the windows generating insets. */ @@ -77,47 +71,19 @@ class InsetsPolicy { private final DisplayContent mDisplayContent; private final DisplayPolicy mPolicy; - /** For resetting visibilities of insets sources. */ - private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() { + /** Used to show system bars transiently. This won't affect the layout. */ + private final InsetsControlTarget mTransientControlTarget; - @Override - public void notifyInsetsControlChanged() { - boolean hasLeash = false; - final InsetsSourceControl[] controls = - mStateController.getControlsForDispatch(this); - if (controls == null) { - return; - } - for (InsetsSourceControl control : controls) { - if (isTransient(control.getType())) { - // The visibilities of transient bars will be handled with animations. - continue; - } - final SurfaceControl leash = control.getLeash(); - if (leash != null) { - hasLeash = true; - - // We use alpha to control the visibility here which aligns the logic at - // SurfaceAnimator.createAnimationLeash - final boolean visible = - (control.getType() & WindowInsets.Type.defaultVisible()) != 0; - mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f); - } - } - if (hasLeash) { - mDisplayContent.scheduleAnimation(); - } - } - }; + /** Used to show system bars permanently. This will affect the layout. */ + private final InsetsControlTarget mPermanentControlTarget; private WindowState mFocusedWin; private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR); private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR); private @InsetsType int mShowingTransientTypes; - private boolean mAnimatingShown; + private @InsetsType int mForcedShowingTypes; private final boolean mHideNavBarForKeyboard; - private final float[] mTmpFloat9 = new float[9]; InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) { mStateController = stateController; @@ -125,9 +91,12 @@ class InsetsPolicy { mPolicy = displayContent.getDisplayPolicy(); final Resources r = mPolicy.getContext().getResources(); mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard); + mTransientControlTarget = new ControlTarget( + stateController, displayContent.mWmService.mH, "TransientControlTarget"); + mPermanentControlTarget = new ControlTarget( + stateController, displayContent.mWmService.mH, "PermanentControlTarget"); } - /** Updates the target which can control system bars. */ void updateBarControlTarget(@Nullable WindowState focusedWin) { if (mFocusedWin != focusedWin) { @@ -142,13 +111,13 @@ class InsetsPolicy { final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow(); mStateController.onBarControlTargetChanged( statusControlTarget, - statusControlTarget == mDummyControlTarget + statusControlTarget == mTransientControlTarget ? getStatusControlTarget(focusedWin, true /* fake */) : statusControlTarget == notificationShade ? getStatusControlTarget(topApp, true /* fake */) : null, navControlTarget, - navControlTarget == mDummyControlTarget + navControlTarget == mTransientControlTarget ? getNavControlTarget(focusedWin, true /* fake */) : navControlTarget == notificationShade ? getNavControlTarget(topApp, true /* fake */) @@ -198,19 +167,19 @@ class InsetsPolicy { mFocusedWin, (showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0, isGestureOnSystemBar); - - // The leashes can be created while updating bar control target. The surface transaction - // of the new leashes might not be applied yet. The callback posted here ensures we can - // get the valid leashes because the surface transaction will be applied in the next - // animation frame which will be triggered if a new leash is created. - mDisplayContent.mWmService.mAnimator.getChoreographer().postFrameCallback(time -> { - synchronized (mDisplayContent.mWmService.mGlobalLock) { - startAnimation(true /* show */, null /* callback */); - } - }); } } + @VisibleForTesting + InsetsControlTarget getTransientControlTarget() { + return mTransientControlTarget; + } + + @VisibleForTesting + InsetsControlTarget getPermanentControlTarget() { + return mPermanentControlTarget; + } + void hideTransient() { if (mShowingTransientTypes == 0) { return; @@ -221,23 +190,8 @@ class InsetsPolicy { /* areVisible= */ false, /* wereRevealedFromSwipeOnSystemBar= */ false); - startAnimation(false /* show */, () -> { - synchronized (mDisplayContent.mWmService.mGlobalLock) { - final SparseArray<InsetsSourceProvider> providers = - mStateController.getSourceProviders(); - for (int i = providers.size() - 1; i >= 0; i--) { - final InsetsSourceProvider provider = providers.valueAt(i); - if (!isTransient(provider.getSource().getType())) { - continue; - } - // We are about to clear mShowingTransientTypes, we don't want the transient bar - // can cause insets on the client. Restore the client visibility. - provider.setClientVisible(false); - } - mShowingTransientTypes = 0; - updateBarControlTarget(mFocusedWin); - } - }); + mShowingTransientTypes = 0; + updateBarControlTarget(mFocusedWin); } boolean isTransient(@InsetsType int type) { @@ -500,7 +454,7 @@ class InsetsPolicy { private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin, boolean fake) { if (!fake && isTransient(Type.statusBars())) { - return mDummyControlTarget; + return mTransientControlTarget; } final WindowState notificationShade = mPolicy.getNotificationShade(); if (focusedWin == notificationShade) { @@ -514,16 +468,16 @@ class InsetsPolicy { component, focusedWin.getRequestedVisibleTypes()); return mDisplayContent.mRemoteInsetsControlTarget; } - if (mPolicy.areSystemBarsForcedShownLw()) { + if (areTypesForciblyShowing(Type.statusBars())) { // Status bar is forcibly shown. We don't want the client to control the status bar, and // we will dispatch the real visibility of status bar to the client. - return null; + return mPermanentControlTarget; } if (forceShowsStatusBarTransiently() && !fake) { // Status bar is forcibly shown transiently, and its new visibility won't be // dispatched to the client so that we can keep the layout stable. We will dispatch the // fake control to the client, so that it can re-show the bar during this scenario. - return mDummyControlTarget; + return mTransientControlTarget; } if (!canBeTopFullscreenOpaqueWindow(focusedWin) && mPolicy.topAppHidesSystemBar(Type.statusBars()) @@ -554,7 +508,7 @@ class InsetsPolicy { return null; } if (!fake && isTransient(Type.navigationBars())) { - return mDummyControlTarget; + return mTransientControlTarget; } if (focusedWin == mPolicy.getNotificationShade()) { // Notification shade has control anyways, no reason to force anything. @@ -567,13 +521,6 @@ class InsetsPolicy { return focusedWin; } } - if (forcesShowingNavigationBars(focusedWin)) { - // When "force show navigation bar" is enabled, it means both force visible is true, and - // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown - // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could - // still control the navigation bar in this mode. - return null; - } if (remoteInsetsControllerControlsSystemBars(focusedWin)) { ComponentName component = focusedWin.mActivityRecord != null ? focusedWin.mActivityRecord.mActivityComponent : null; @@ -581,16 +528,16 @@ class InsetsPolicy { component, focusedWin.getRequestedVisibleTypes()); return mDisplayContent.mRemoteInsetsControlTarget; } - if (mPolicy.areSystemBarsForcedShownLw()) { + if (areTypesForciblyShowing(Type.navigationBars())) { // Navigation bar is forcibly shown. We don't want the client to control the navigation // bar, and we will dispatch the real visibility of navigation bar to the client. - return null; + return mPermanentControlTarget; } if (forceShowsNavigationBarTransiently() && !fake) { // Navigation bar is forcibly shown transiently, and its new visibility won't be // dispatched to the client so that we can keep the layout stable. We will dispatch the // fake control to the client, so that it can re-show the bar during this scenario. - return mDummyControlTarget; + return mTransientControlTarget; } final WindowState notificationShade = mPolicy.getNotificationShade(); if (!canBeTopFullscreenOpaqueWindow(focusedWin) @@ -603,7 +550,32 @@ class InsetsPolicy { return focusedWin; } - boolean forcesShowingNavigationBars(WindowState win) { + boolean areTypesForciblyShowing(@InsetsType int types) { + return (mForcedShowingTypes & types) == types; + } + + void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) { + mForcedShowingTypes = (inSplitScreenMode || inFreeformMode) + ? (Type.statusBars() | Type.navigationBars()) + : forceShowingNavigationBars(win) + ? Type.navigationBars() + : 0; + + // The client app won't be able to control these types of system bars. Here makes the client + // forcibly consume these types to prevent the app content from getting obscured. + mStateController.setForcedConsumingTypes( + mForcedShowingTypes | (remoteInsetsControllerControlsSystemBars(win) + ? (Type.statusBars() | Type.navigationBars()) + : 0)); + + updateBarControlTarget(win); + } + + private boolean forceShowingNavigationBars(WindowState win) { + // When "force show navigation bar" is enabled, it means both force visible is true, and + // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown + // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could + // still control the navigation bar in this mode. return mPolicy.isForceShowNavigationBarEnabled() && win != null && win.getActivityType() == ACTIVITY_TYPE_STANDARD; } @@ -642,34 +614,6 @@ class InsetsPolicy { && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0; } - @VisibleForTesting - void startAnimation(boolean show, Runnable callback) { - @InsetsType int typesReady = 0; - final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>(); - final InsetsSourceControl[] controls = - mStateController.getControlsForDispatch(mDummyControlTarget); - if (controls == null) { - if (callback != null) { - DisplayThread.getHandler().post(callback); - } - return; - } - for (InsetsSourceControl control : controls) { - if (isTransient(control.getType()) && control.getLeash() != null) { - typesReady |= control.getType(); - controlsReady.put(control.getId(), new InsetsSourceControl(control)); - } - } - controlAnimationUnchecked(typesReady, controlsReady, show, callback); - } - - private void controlAnimationUnchecked(int typesReady, - SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) { - InsetsPolicyAnimationControlListener listener = - new InsetsPolicyAnimationControlListener(show, callback, typesReady); - listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show); - } - private void dispatchTransientSystemBarsVisibilityChanged( @Nullable WindowState focusedWindow, boolean areVisible, @@ -696,6 +640,21 @@ class InsetsPolicy { wereRevealedFromSwipeOnSystemBar); } + void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "InsetsPolicy"); + prefix = prefix + " "; + pw.println(prefix + "status: " + StatusBarManager.windowStateToString(mStatusBar.mState)); + pw.println(prefix + "nav: " + StatusBarManager.windowStateToString(mNavBar.mState)); + if (mShowingTransientTypes != 0) { + pw.println(prefix + "mShowingTransientTypes=" + + WindowInsets.Type.toString(mShowingTransientTypes)); + } + if (mForcedShowingTypes != 0) { + pw.println(prefix + "mForcedShowingTypes=" + + WindowInsets.Type.toString(mForcedShowingTypes)); + } + } + private class BarWindow { private final int mId; @@ -725,105 +684,138 @@ class InsetsPolicy { } } - private class InsetsPolicyAnimationControlListener extends - InsetsController.InternalAnimationControlListener { - Runnable mFinishCallback; - InsetsPolicyAnimationControlCallbacks mControlCallbacks; + private static class ControlTarget implements InsetsControlTarget { + + private final InsetsState mState = new InsetsState(); + private final InsetsController mInsetsController; + private final InsetsStateController mStateController; + private final String mName; - InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) { - super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE, - false /* disable */, 0 /* floatingImeBottomInsets */, - null /* loggingListener */, null /* jankContext */); - mFinishCallback = finishCallback; - mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); + ControlTarget(InsetsStateController stateController, Handler handler, String name) { + mStateController = stateController; + mInsetsController = new InsetsController(new Host(handler, name)); + mName = name; } @Override - protected void onAnimationFinish() { - super.onAnimationFinish(); - if (mFinishCallback != null) { - DisplayThread.getHandler().post(mFinishCallback); - } + public void notifyInsetsControlChanged() { + mState.set(mStateController.getRawInsetsState(), true /* copySources */); + mInsetsController.onStateChanged(mState); + mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this)); } - private class InsetsPolicyAnimationControlCallbacks implements - InsetsAnimationControlCallbacks { - private InsetsAnimationControlImpl mAnimationControl = null; - private InsetsPolicyAnimationControlListener mListener; + @Override + public String toString() { + return mName; + } + } - InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) { - mListener = listener; - } + private static class Host implements InsetsController.Host { - private void controlAnimationUnchecked(int typesReady, - SparseArray<InsetsSourceControl> controls, boolean show) { - if (typesReady == 0) { - // nothing to animate. - return; - } - mAnimatingShown = show; - - final InsetsState state = mFocusedWin.getInsetsState(); - - // We are about to playing the default animation. Passing a null frame indicates - // the controlled types should be animated regardless of the frame. - mAnimationControl = new InsetsAnimationControlImpl(controls, - null /* frame */, state, mListener, typesReady, this, - mListener.getDurationMs(), getInsetsInterpolator(), - show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show - ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN - : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, - null /* translator */, null /* statsToken */); - SurfaceAnimationThread.getHandler().post( - () -> mListener.onReady(mAnimationControl, typesReady)); - } + private final float[] mTmpFloat9 = new float[9]; + private final Handler mHandler; + private final String mName; - /** Called on SurfaceAnimationThread without global WM lock held. */ - @Override - public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) { - if (mAnimationControl.applyChangeInsets(null /* outState */)) { - mAnimationControl.finish(mAnimatingShown); - } - } + Host(Handler handler, String name) { + mHandler = handler; + mName = name; + } - @Override - public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) { - // Nothing's needed here. Finish steps is handled in the listener - // onAnimationFinished callback. - } + @Override + public Handler getHandler() { + return mHandler; + } - /** Called on SurfaceAnimationThread without global WM lock held. */ - @Override - public void applySurfaceParams( - final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = params.length - 1; i >= 0; i--) { - SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i]; - applyParams(t, surfaceParams, mTmpFloat9); - } - t.apply(); - t.close(); - } + @Override + public void notifyInsetsChanged() { } - // Since we don't push applySurfaceParams to a Handler-queue we don't need - // to push release in this case. - @Override - public void releaseSurfaceControlFromRt(SurfaceControl sc) { - sc.release(); - } + @Override + public void dispatchWindowInsetsAnimationPrepare( + @NonNull WindowInsetsAnimation animation) { } - @Override - public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController> - void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types, - WindowInsetsAnimation animation, - Bounds bounds) { - } + @Override + public Bounds dispatchWindowInsetsAnimationStart( + @NonNull WindowInsetsAnimation animation, + @NonNull Bounds bounds) { + return bounds; + } + + @Override + public WindowInsets dispatchWindowInsetsAnimationProgress( + @NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + return insets; + } + + @Override + public void dispatchWindowInsetsAnimationEnd( + @NonNull WindowInsetsAnimation animation) { } - @Override - public void reportPerceptible(int types, boolean perceptible) { - // No-op for now - only client windows report perceptibility for now, with policy - // controllers assumed to always be perceptible. + @Override + public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... p) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = p.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.applyParams(t, p[i], mTmpFloat9); } + t.apply(); + t.close(); + } + + @Override + public void updateRequestedVisibleTypes(int types) { } + + @Override + public boolean hasAnimationCallbacks() { + return false; + } + + @Override + public void setSystemBarsAppearance(int appearance, int mask) { } + + @Override + public int getSystemBarsAppearance() { + return 0; + } + + @Override + public void setSystemBarsBehavior(int behavior) { } + + @Override + public int getSystemBarsBehavior() { + return BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + } + + @Override + public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) { + surfaceControl.release(); + } + + @Override + public void addOnPreDrawRunnable(Runnable r) { } + + @Override + public void postInsetsAnimationCallback(Runnable r) { } + + @Override + public InputMethodManager getInputMethodManager() { + return null; + } + + @Nullable + @Override + public String getRootViewTitle() { + return mName; + } + + @Override + public int dipToPx(int dips) { + return 0; + } + + @Nullable + @Override + public IBinder getWindowToken() { + return null; } } } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index e1c865bb85be..2b8312c3ea60 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -40,6 +40,7 @@ import android.graphics.Rect; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; +import android.view.InsetsSource.Flags; import android.view.InsetsSourceControl; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -81,6 +82,8 @@ class InsetsSourceProvider { private final Rect mSourceFrame = new Rect(); private final Rect mLastSourceFrame = new Rect(); private @NonNull Insets mInsetsHint = Insets.NONE; + private @Flags int mFlagsFromFrameProvider; + private @Flags int mFlagsFromServer; private final Consumer<Transaction> mSetLeashPositionConsumer = t -> { if (mControl != null) { @@ -189,6 +192,16 @@ class InsetsSourceProvider { } } + boolean setFlags(@Flags int flags, @Flags int mask) { + mFlagsFromServer = (mFlagsFromServer & ~mask) | (flags & mask); + final @Flags int mergedFlags = mFlagsFromFrameProvider | mFlagsFromServer; + if (mSource.getFlags() != mergedFlags) { + mSource.setFlags(mergedFlags); + return true; + } + return false; + } + /** * The source frame can affect the layout of other windows, so this should be called once the * window container gets laid out. @@ -217,11 +230,11 @@ class InsetsSourceProvider { mSourceFrame.set(frame); if (mFrameProvider != null) { - final int flags = mFrameProvider.apply( + mFlagsFromFrameProvider = mFrameProvider.apply( mWindowContainer.getDisplayContent().mDisplayFrames, mWindowContainer, mSourceFrame); - mSource.setFlags(flags); + mSource.setFlags(mFlagsFromFrameProvider | mFlagsFromServer); } updateSourceFrameForServerVisibility(); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index addb5219c663..081ebe0e7cbd 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.ID_IME; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; @@ -85,6 +86,8 @@ class InsetsStateController { } }; + private @InsetsType int mForcedConsumingTypes; + InsetsStateController(DisplayContent displayContent) { mDisplayContent = displayContent; } @@ -122,6 +125,11 @@ class InsetsStateController { provider = id == ID_IME ? new ImeInsetsSourceProvider(source, this, mDisplayContent) : new InsetsSourceProvider(source, this, mDisplayContent); + provider.setFlags( + (mForcedConsumingTypes & type) != 0 + ? FLAG_FORCE_CONSUMING + : 0, + FLAG_FORCE_CONSUMING); mProviders.put(id, provider); return provider; } @@ -137,6 +145,24 @@ class InsetsStateController { } } + void setForcedConsumingTypes(@InsetsType int types) { + if (mForcedConsumingTypes != types) { + mForcedConsumingTypes = types; + boolean changed = false; + for (int i = mProviders.size() - 1; i >= 0; i--) { + final InsetsSourceProvider provider = mProviders.valueAt(i); + changed |= provider.setFlags( + (types & provider.getSource().getType()) != 0 + ? FLAG_FORCE_CONSUMING + : 0, + FLAG_FORCE_CONSUMING); + } + if (changed) { + notifyInsetsChanged(); + } + } + } + /** * Called when a layout pass has occurred. */ @@ -391,6 +417,10 @@ class InsetsStateController { for (int i = mProviders.size() - 1; i >= 0; i--) { mProviders.valueAt(i).dump(pw, prefix + " "); } + if (mForcedConsumingTypes != 0) { + pw.println(prefix + "mForcedConsumingTypes=" + + WindowInsets.Type.toString(mForcedConsumingTypes)); + } } void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index ad9c3b274267..83fd725aafdf 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -418,13 +418,17 @@ class KeyguardController { return; } - final boolean waitAppTransition = isKeyguardLocked(displayId); - mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY), - waitAppTransition); - if (waitAppTransition) { - mService.deferWindowLayout(); - try { - if (isDisplayOccluded(DEFAULT_DISPLAY)) { + final TransitionController tc = mRootWindowContainer.mTransitionController; + + final boolean occluded = isDisplayOccluded(displayId); + final boolean performTransition = isKeyguardLocked(displayId); + final boolean executeTransition = performTransition && !tc.isCollecting(); + + mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded); + mService.deferWindowLayout(); + try { + if (isKeyguardLocked(displayId)) { + if (occluded) { mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_FLAG_KEYGUARD_OCCLUDING, @@ -434,11 +438,19 @@ class KeyguardController { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_FLAG_KEYGUARD_UNOCCLUDING); } - updateKeyguardSleepToken(DEFAULT_DISPLAY); + } else { + if (tc.inTransition()) { + tc.mStateValidators.add(mWindowManager.mPolicy::applyKeyguardOcclusionChange); + } else { + mWindowManager.mPolicy.applyKeyguardOcclusionChange(); + } + } + updateKeyguardSleepToken(displayId); + if (performTransition && executeTransition) { mWindowManager.executeAppTransition(); - } finally { - mService.continueWindowLayout(); } + } finally { + mService.continueWindowLayout(); } } @@ -485,6 +497,9 @@ class KeyguardController { } } + /** + * @return true if Keyguard is occluded or the device is dreaming. + */ boolean isDisplayOccluded(int displayId) { return getDisplayState(displayId).mOccluded; } diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 586077658a26..c914fa10687f 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -48,6 +48,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; import android.view.RemoteAnimationAdapter; +import android.window.RemoteTransition; import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; @@ -385,6 +386,18 @@ public class SafeActivityOptions { throw new SecurityException(msg); } + // Check permission for remote transitions + final RemoteTransition transition = options.getRemoteTransition(); + if (transition != null && supervisor.mService.checkPermission( + CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid) + != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + getIntentString(intent) + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with remoteTransition"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + // If launched from bubble is specified, then ensure that the caller is system or sysui. if (options.getLaunchedFromBubble() && !isSystemOrSystemUI(callingPid, callingUid)) { final String msg = "Permission Denial: starting " + getIntentString(intent) diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f2e0260a5b84..5b3bbd50cb2d 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -27,6 +27,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; +import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -613,12 +615,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } if (mParticipants.contains(wc)) return; - // Wallpaper is like in a static drawn state unless display may have changes, so exclude - // the case to reduce transition latency waiting for the unchanged wallpaper to redraw. - final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent)) - // Transient-hide may be hidden later, so no need to request redraw. - && !isInTransientHide(wc); - if (needSync) { + // Transient-hide may be hidden later, so no need to request redraw. + if (!isInTransientHide(wc)) { mSyncEngine.addToSyncSet(mSyncId, wc); } ChangeInfo info = mChanges.get(wc); @@ -2667,7 +2665,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } private void validateKeyguardOcclusion() { - if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { + if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { mController.mStateValidators.add( mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange); } @@ -2696,6 +2694,26 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { }); } + /** + * Returns {@code true} if the transition and the corresponding transaction should be applied + * on display thread. Currently, this only checks for display rotation change because the order + * of dispatching the new display info will be after requesting the windows to sync drawing. + * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also, + * because the display thread has a higher priority, it is faster to perform the configuration + * changes and window hierarchy traversal. + */ + boolean shouldApplyOnDisplayThread() { + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent(); + if (dc == null) continue; + final ChangeInfo changeInfo = mChanges.get(dc); + if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) { + return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper(); + } + } + return false; + } + /** Applies the new configuration for the changed displays. */ void applyDisplayChangeIfNeeded() { for (int i = mParticipants.size() - 1; i >= 0; --i) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a539a4893d4f..79cb61be5948 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -588,15 +588,6 @@ class TransitionController { /** Sets the sync method for the display change. */ private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange, @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) { - final int startRotation = displayChange.getStartRotation(); - final int endRotation = displayChange.getEndRotation(); - if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) { - // 180 degrees rotation change may not change screen size. So the clients may draw - // some frames before and after the display projection transaction is applied by the - // remote player. That may cause some buffers to show in different rotation. So use - // sync method to pause clients drawing until the projection transaction is applied. - mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST); - } final Rect startBounds = displayChange.getStartAbsBounds(); final Rect endBounds = displayChange.getEndAbsBounds(); if (startBounds == null || endBounds == null) return; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 9e7df004806b..805e7ffe7d76 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -30,6 +30,7 @@ import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.Bundle; import android.os.IBinder; +import android.os.Message; import android.util.Pair; import android.view.ContentRecordingSession; import android.view.Display; @@ -515,12 +516,13 @@ public abstract class WindowManagerInternal { * Invalidate all visible windows on a given display, and report back on the callback when all * windows have redrawn. * - * @param callback reporting callback to be called when all windows have redrawn. + * @param message The message will be sent when all windows have redrawn. Note that the message + * must be obtained from handler, otherwise it will throw NPE. * @param timeout calls the callback anyway after the timeout. * @param displayId waits for the windows on the given display, INVALID_DISPLAY to wait for all * windows on all displays. */ - public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId); + public abstract void waitForAllWindowsDrawn(Message message, long timeout, int displayId); /** * Overrides the display size. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index bb3d10912724..0e19671afd82 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -601,7 +601,7 @@ public class WindowManagerService extends IWindowManager.Stub * The callbacks to make when the windows all have been drawn for a given * {@link WindowContainer}. */ - final HashMap<WindowContainer, Runnable> mWaitingForDrawnCallbacks = new HashMap<>(); + final ArrayMap<WindowContainer<?>, Message> mWaitingForDrawnCallbacks = new ArrayMap<>(); /** List of window currently causing non-system overlay windows to be hidden. */ private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>(); @@ -763,8 +763,6 @@ public class WindowManagerService extends IWindowManager.Stub Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS); private final Uri mPolicyControlUri = Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL); - private final Uri mPointerLocationUri = - Settings.System.getUriFor(Settings.System.POINTER_LOCATION); private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor( Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS); private final Uri mFreeformWindowUri = Settings.Global.getUriFor( @@ -792,7 +790,6 @@ public class WindowManagerService extends IWindowManager.Stub resolver.registerContentObserver(mImmersiveModeConfirmationsUri, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(mPointerLocationUri, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this, UserHandle.USER_ALL); resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL); @@ -816,11 +813,6 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (mPointerLocationUri.equals(uri)) { - updatePointerLocation(); - return; - } - if (mForceDesktopModeOnExternalDisplaysUri.equals(uri)) { updateForceDesktopModeOnExternalDisplays(); return; @@ -869,7 +861,6 @@ public class WindowManagerService extends IWindowManager.Stub void loadSettings() { updateSystemUiSettings(false /* handleChange */); - updatePointerLocation(); updateMaximumObscuringOpacityForTouch(); } @@ -900,21 +891,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void updatePointerLocation() { - ContentResolver resolver = mContext.getContentResolver(); - final boolean enablePointerLocation = Settings.System.getIntForUser(resolver, - Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT) != 0; - - if (mPointerLocationEnabled == enablePointerLocation) { - return; - } - mPointerLocationEnabled = enablePointerLocation; - synchronized (mGlobalLock) { - mRoot.forAllDisplayPolicies( - p -> p.setPointerLocationEnabled(mPointerLocationEnabled)); - } - } - void updateForceDesktopModeOnExternalDisplays() { ContentResolver resolver = mContext.getContentResolver(); final boolean enableForceDesktopMode = Settings.Global.getInt(resolver, @@ -5369,8 +5345,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int NOTIFY_ACTIVITY_DRAWN = 32; - public static final int ALL_WINDOWS_DRAWN = 33; - public static final int NEW_ANIMATOR_SCALE = 34; public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36; @@ -5492,7 +5466,7 @@ public class WindowManagerService extends IWindowManager.Stub } case WAITING_FOR_DRAWN_TIMEOUT: { - Runnable callback = null; + final Message callback; final WindowContainer<?> container = (WindowContainer<?>) msg.obj; synchronized (mGlobalLock) { ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s", @@ -5506,7 +5480,7 @@ public class WindowManagerService extends IWindowManager.Stub callback = mWaitingForDrawnCallbacks.remove(container); } if (callback != null) { - callback.run(); + callback.sendToTarget(); } break; } @@ -5530,17 +5504,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case ALL_WINDOWS_DRAWN: { - Runnable callback; - final WindowContainer container = (WindowContainer) msg.obj; - synchronized (mGlobalLock) { - callback = mWaitingForDrawnCallbacks.remove(container); - } - if (callback != null) { - callback.run(); - } - break; - } case NEW_ANIMATOR_SCALE: { float scale = getCurrentAnimatorScale(); ValueAnimator.setDurationScale(scale); @@ -6098,7 +6061,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mWaitingForDrawnCallbacks.isEmpty()) { return; } - mWaitingForDrawnCallbacks.forEach((container, callback) -> { + for (int i = mWaitingForDrawnCallbacks.size() - 1; i >= 0; i--) { + final WindowContainer<?> container = mWaitingForDrawnCallbacks.keyAt(i); for (int j = container.mWaitingForDrawn.size() - 1; j >= 0; j--) { final WindowState win = (WindowState) container.mWaitingForDrawn.get(j); ProtoLog.i(WM_DEBUG_SCREEN_ON, @@ -6124,9 +6088,9 @@ public class WindowManagerService extends IWindowManager.Stub if (container.mWaitingForDrawn.isEmpty()) { ProtoLog.d(WM_DEBUG_SCREEN_ON, "All windows drawn!"); mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container); - mH.sendMessage(mH.obtainMessage(H.ALL_WINDOWS_DRAWN, container)); + mWaitingForDrawnCallbacks.removeAt(i).sendToTarget(); } - }); + } } private void traceStartWaitingForWindowDrawn(WindowState window) { @@ -7842,13 +7806,14 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) { + public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) { + Objects.requireNonNull(message.getTarget()); final WindowContainer<?> container = displayId == INVALID_DISPLAY ? mRoot : mRoot.getDisplayContent(displayId); if (container == null) { // The waiting container doesn't exist, no need to wait to run the callback. Run and // return; - callback.run(); + message.sendToTarget(); return; } boolean allWindowsDrawn = false; @@ -7865,13 +7830,13 @@ public class WindowManagerService extends IWindowManager.Stub } } - mWaitingForDrawnCallbacks.put(container, callback); + mWaitingForDrawnCallbacks.put(container, message); mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout); checkDrawnWindowsLocked(); } } if (allWindowsDrawn) { - callback.run(); + message.sendToTarget(); } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a2f7ba499fdf..31918f478e44 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -321,9 +321,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller); return transition.getToken(); } - transition.start(); transition.mLogger.mStartWCT = wct; - applyTransaction(wct, -1 /*syncId*/, transition, caller); + if (transition.shouldApplyOnDisplayThread()) { + mService.mH.post(() -> { + synchronized (mService.mGlobalLock) { + transition.start(); + applyTransaction(wct, -1 /* syncId */, transition, caller); + } + }); + } else { + transition.start(); + applyTransaction(wct, -1 /* syncId */, transition, caller); + } // Since the transition is already provided, it means WMCore is determining the // "readiness lifecycle" outside the provided transaction, so don't set ready here. return transition.getToken(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index cf1e51fb4e94..0d4c2d631b2c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5665,7 +5665,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } if (mIsWallpaper) { // TODO(b/233286785): Add sync support to wallpaper. - return false; + return true; } // In the WindowContainer implementation we immediately mark ready // since a generic WindowContainer only needs to wait for its diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 46c90b4bc731..bafa4a56b28a 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -126,6 +126,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } protected ProviderRegistryGetSession(@NonNull Context context, @@ -143,6 +144,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } private List<Entry> prepareUiCredentialEntries( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 417fc3949f2c..fe913b9807cf 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -16229,6 +16229,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) { + return DevicePolicyManagerService.this.getDeviceOwnerComponent(callingUserOnly); + } + + @Override public int getDeviceOwnerUserId() { return DevicePolicyManagerService.this.getDeviceOwnerUserId(); } diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java index e33ca7775e22..b7a0cf389396 100644 --- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java +++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java @@ -138,6 +138,14 @@ public class CrossUserPackageVisibilityTests { } @Test + public void testGetUserMinAspectRatio_withCrossUserId() { + final int crossUserId = UserHandle.myUserId() + 1; + assertThrows(SecurityException.class, + () -> mIPackageManager.getUserMinAspectRatio( + mInstrumentation.getContext().getPackageName(), crossUserId)); + } + + @Test public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception { final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName()); assertThrows(IllegalArgumentException.class, diff --git a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 Binary files differindex 30bb6478d18d..6feebb8c833c 100644 --- a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 +++ b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java index 435f0d7f5f15..a805e5c9f87e 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java @@ -20,6 +20,7 @@ import static com.android.compatibility.common.util.ShellUtils.runShellCommand; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static java.lang.reflect.Modifier.isFinal; @@ -138,7 +139,7 @@ public class PackageManagerServiceTest { .setSecondaryCpuAbiString("secondaryCpuAbiString") .setCpuAbiOverrideString("cpuAbiOverrideString") .build(); - pri.populateUsers(new int[] { + pri.populateUsers(new int[]{ 1, 2, 3, 4, 5 }, setting); Assert.assertNotNull(pri.mBroadcastUsers); @@ -150,7 +151,7 @@ public class PackageManagerServiceTest { pri.mBroadcastUsers = null; final int EXCLUDED_USER_ID = 4; setting.setInstantApp(true, EXCLUDED_USER_ID); - pri.populateUsers(new int[] { + pri.populateUsers(new int[]{ 1, 2, 3, EXCLUDED_USER_ID, 5 }, setting); Assert.assertNotNull(pri.mBroadcastUsers); @@ -164,8 +165,8 @@ public class PackageManagerServiceTest { @Test public void testPartitions() { - String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" }; - String[] appdir = { "app", "priv-app" }; + String[] partitions = {"system", "vendor", "odm", "oem", "product", "system_ext"}; + String[] appdir = {"app", "priv-app"}; for (int i = 0; i < partitions.length; i++) { final ScanPartition scanPartition = PackageManagerService.SYSTEM_PARTITIONS.get(i); @@ -425,10 +426,10 @@ public class PackageManagerServiceTest { private String displayName(Method m) { String r = m.getName(); String p = Arrays.toString(m.getGenericParameterTypes()) - .replaceAll("([a-zA-Z0-9]+\\.)+", "") - .replace("class ", "") - .replaceAll("^\\[", "(") - .replaceAll("\\]$", ")"); + .replaceAll("([a-zA-Z0-9]+\\.)+", "") + .replace("class ", "") + .replaceAll("^\\[", "(") + .replaceAll("\\]$", ")"); return r + p; } @@ -612,4 +613,22 @@ public class PackageManagerServiceTest { runShellCommand("pm uninstall " + TEST_PKG_NAME); } } + + @Test + public void testSetUserMinAspectRatio_samePackage_succeeds() throws Exception { + mIPackageManager.setUserMinAspectRatio(PACKAGE_NAME, UserHandle.myUserId(), + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); + // Invoking setUserMinAspectRatio on the same package shouldn't get any exception. + } + + @Test + public void testSetUserMinAspectRatio_differentPackage_fails() { + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + runShellCommand("pm install " + testApk); + assertThrows(SecurityException.class, () -> { + mIPackageManager.setUserMinAspectRatio(TEST_PKG_NAME, UserHandle.myUserId(), + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); + }); + runShellCommand("pm uninstall " + TEST_PKG_NAME); + } } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 836f8581e8eb..16fb012b2f40 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -971,7 +971,7 @@ public class PackageManagerSettingsTests { origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false, false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}), new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning", - "splashScreenTheme", 1000L); + "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET); final PersistableBundle appExtras1 = createPersistableBundle( PACKAGE_NAME_1, 1L, 0.01, true, "appString1"); final PersistableBundle launcherExtras1 = createPersistableBundle( @@ -1638,7 +1638,8 @@ public class PackageManagerSettingsTests { : oldUserState.getSharedLibraryOverlayPaths() == null) && userState.getSplashScreenTheme().equals( oldUserState.getSplashScreenTheme()) - && userState.getUninstallReason() == oldUserState.getUninstallReason(); + && userState.getUninstallReason() == oldUserState.getUninstallReason() + && userState.getMinAspectRatio() == oldUserState.getMinAspectRatio(); } private SharedUserSetting createSharedUserSetting(Settings settings, String userName, diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index f89f73c98cfd..aa0a2fea1a5a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -1257,6 +1257,17 @@ public class LocalDisplayAdapterTest { public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; } + + // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay) + // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure + // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting + // consistent behaviour. Please also note that context passed to this method, is + // mMockContext and values will be loaded from mMockResources. + @Override + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, isFirstDisplay); + } } private class TestListener implements DisplayAdapter.Listener { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index e7b3e6f88dce..617e4ebf11ad 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -325,6 +325,83 @@ public final class UserManagerServiceTest { () -> mUmi.getBootUser(/* waitUntilSet= */ false)); } + @Test + public void testGetPreviousFullUserToEnterForeground() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + assertWithMessage("getPreviousFullUserToEnterForeground") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(OTHER_USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mockCurrentUser(OTHER_USER_ID); + assertWithMessage("getPreviousFullUserToEnterForeground should skip current user") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL; + assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.partial = true; + assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED; + assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUms.addRemovingUserId(OTHER_USER_ID); + assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + private void mockCurrentUser(@UserIdInt int userId) { mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal); diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java index a3a49d7035d9..f3aa4274b3cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java @@ -259,6 +259,29 @@ public class AlarmQueueTest { } @Test + public void testMinTimeBetweenAlarms_freshAlarm() { + final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS); + final long fixedTimeElapsed = mInjector.getElapsedRealtime(); + + InOrder inOrder = inOrder(mAlarmManager); + + final String pkg1 = "com.android.test.1"; + final String pkg2 = "com.android.test.2"; + alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS); + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( + anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); + + advanceElapsedClock(MINUTE_IN_MILLIS); + + alarmQueue.onAlarm(); + // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the + // first. + alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS); + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( + anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); + } + + @Test public void testOnAlarm() { final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); final long nowElapsed = mInjector.getElapsedRealtime(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index e6ef044d4791..5b5c8d415f84 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -202,6 +203,7 @@ public class FullScreenMagnificationControllerTest { assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0)); assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1)); + // Once for each display on unregister verify(mMockThumbnail, times(2)).hideThumbnail(); } @@ -543,7 +545,11 @@ public class FullScreenMagnificationControllerTest { // The first time is triggered when the thumbnail is just created. // The second time is triggered when the magnification region changed. verify(mMockThumbnail, times(2)).setThumbnailBounds( - any(), anyFloat(), anyFloat(), anyFloat()); + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -681,6 +687,9 @@ public class FullScreenMagnificationControllerTest { checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId); assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2)); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId); + + // Once on init before it's activated and once for reset + verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @@ -783,6 +792,9 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1); + + // Twice for each display: once on init before it's activated and once for screen off + verify(mMockThumbnail, times(4)).hideThumbnail(); } @Test @@ -847,6 +859,15 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); checkActivatedAndMagnifying( /* activated= */ expectedActivated, /* magnifying= */ false, displayId); + + if (expectedActivated) { + verify(mMockThumbnail, times(2)).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); + } } @Test @@ -950,6 +971,13 @@ public class FullScreenMagnificationControllerTest { INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + eq(scale), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -984,6 +1012,13 @@ public class FullScreenMagnificationControllerTest { INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + eq(scale), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -1246,6 +1281,13 @@ public class FullScreenMagnificationControllerTest { callbacks.onImeWindowVisibilityChanged(true); mMessageCapturingHandler.sendAllMessages(); verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true)); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -1270,6 +1312,15 @@ public class FullScreenMagnificationControllerTest { mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0); verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false)); + verify(mMockThumbnail).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); + + // Once on init before it's activated and once for reset + verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @@ -1281,6 +1332,12 @@ public class FullScreenMagnificationControllerTest { assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0); assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0)); + verify(mMockThumbnail).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } private void setScaleToMagnifying() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index e75fc2149080..81dd96195ee0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -666,6 +666,21 @@ public class MagnificationControllerTest { assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0); assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0); assertEquals(config.getScale(), actualConfig.getScale(), 0); + + verify(mWindowMagnificationManager).onUserMagnificationScaleChanged( + /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(config.getScale())); + } + + @Test + public void onSourceBoundChanged_windowEnabled_notifyMagnificationChanged() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + reset(mWindowMagnificationManager); + + mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, TEST_RECT); + + verify(mWindowMagnificationManager).onUserMagnificationScaleChanged( + /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(DEFAULT_SCALE)); } @Test @@ -794,6 +809,7 @@ public class MagnificationControllerTest { verify(mMagnificationController).onWindowMagnificationActivationState( eq(TEST_DISPLAY), eq(true)); } + @Test public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage() throws RemoteException { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java index 3baa102b882b..8faddf8ff541 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java @@ -187,6 +187,29 @@ public class MagnificationThumbnailTest { .addView(eq(mMagnificationThumbnail.mThumbnailLayout), any()); verify(mMockWindowManager, never()) .removeView(eq(mMagnificationThumbnail.mThumbnailLayout)); + verify(mMockWindowManager, never()) + .updateViewLayout(eq(mMagnificationThumbnail.mThumbnailLayout), any()); + } + + @Test + public void whenVisible_setBoundsUpdatesLayout() throws InterruptedException { + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( + /* scale= */ 2f, + /* centerX= */ 5, + /* centerY= */ 10 + )); + runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds( + new Rect(), + /* scale= */ 2f, + /* centerX= */ 5, + /* centerY= */ 10 + )); + idle(); + + verify(mMockWindowManager).updateViewLayout( + eq(mMagnificationThumbnail.mThumbnailLayout), + /* params= */ any() + ); } private static void idle() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java index 2357e65871e6..860819911173 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java @@ -122,6 +122,15 @@ public class WindowMagnificationConnectionWrapperTest { } @Test + public void onUserMagnificationScaleChanged() throws RemoteException { + final int testUserId = 1; + final float testScale = 3f; + mConnectionWrapper.onUserMagnificationScaleChanged(testUserId, TEST_DISPLAY, testScale); + verify(mConnection).onUserMagnificationScaleChanged( + eq(testUserId), eq(TEST_DISPLAY), eq(testScale)); + } + + @Test public void setMirrorWindowCallback() throws RemoteException { mConnectionWrapper.setConnectionCallback(mCallback); verify(mConnection).setConnectionCallback(mCallback); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index c98de7c4d00e..e8b337ab97a7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -548,6 +548,18 @@ public class WindowMagnificationManagerTest { } @Test + public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod() + throws RemoteException { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + + final float testScale = 3f; + mWindowMagnificationManager.onUserMagnificationScaleChanged( + CURRENT_USER_ID, TEST_DISPLAY, testScale); + verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged( + eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale)); + } + + @Test public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index dccacb4d301a..24a628eb4331 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -1342,6 +1342,10 @@ public class UserControllerTest { Message copy = new Message(); copy.copyFrom(msg); mMessages.add(copy); + if (msg.getCallback() != null) { + msg.getCallback().run(); + msg.setCallback(null); + } return super.sendMessageAtTime(msg, uptimeMillis); } } diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index c42928eba85f..bb8b986c6f61 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.app.ActivityManagerInternal; @@ -49,10 +50,14 @@ import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager.NameNotFoundException; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; +import android.media.projection.IMediaProjectionWatcherCallback; import android.media.projection.ReviewGrantedConsentResult; +import android.os.Binder; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.view.ContentRecordingSession; @@ -86,6 +91,7 @@ public class MediaProjectionManagerServiceTest { private static final int UID = 10; private static final String PACKAGE_NAME = "test.package"; private final ApplicationInfo mAppInfo = new ApplicationInfo(); + private final TestLooper mTestLooper = new TestLooper(); private static final ContentRecordingSession DISPLAY_SESSION = ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY); // Callback registered by an app on a MediaProjection instance. @@ -110,6 +116,14 @@ public class MediaProjectionManagerServiceTest { } }; + private final MediaProjectionManagerService.Injector mTestLooperInjector = + new MediaProjectionManagerService.Injector() { + @Override + Looper createCallbackLooper() { + return mTestLooper.getLooper(); + } + }; + private Context mContext; private MediaProjectionManagerService mService; private OffsettableClock mClock; @@ -122,12 +136,15 @@ public class MediaProjectionManagerServiceTest { private WindowManagerInternal mWindowManagerInternal; @Mock private PackageManager mPackageManager; + @Mock + private IMediaProjectionWatcherCallback mWatcherCallback; @Captor private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + when(mWatcherCallback.asBinder()).thenReturn(new Binder()); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternal); @@ -671,6 +688,59 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.isCurrentProjection(projection)).isTrue(); } + @Test + public void setContentRecordingSession_successful_notifiesListeners() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + mService.setContentRecordingSession(DISPLAY_SESSION); + + verify(mWatcherCallback).onRecordingSessionSet( + projection.getProjectionInfo(), + DISPLAY_SESSION + ); + } + + @Test + public void setContentRecordingSession_notifiesListenersOnCallbackLooper() + throws Exception { + mService = new MediaProjectionManagerService(mContext, mTestLooperInjector); + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + + mService.setContentRecordingSession(DISPLAY_SESSION); + // Callback not notified yet, as test looper hasn't dispatched the message yet + verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any()); + + mTestLooper.dispatchAll(); + // Message dispatched on test looper. Callback should now be notified. + verify(mWatcherCallback).onRecordingSessionSet( + projection.getProjectionInfo(), + DISPLAY_SESSION + ); + } + + @Test + public void setContentRecordingSession_failure_doesNotNotifyListeners() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(false).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + mService.setContentRecordingSession(DISPLAY_SESSION); + + verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any()); + } + private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) { verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession( mSessionCaptor.capture()); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java index fdf94bec8c18..39cc6537c759 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java @@ -182,7 +182,7 @@ public class UserManagerServiceCreateProfileTest { UserInfo secondaryUser = addUser(); UserInfo profile = addProfile(secondaryUser); // Add the profile it to the users being removed. - mUserManagerService.addRemovingUserIdLocked(profile.id); + mUserManagerService.addRemovingUserId(profile.id); // We should reuse the badge from the profile being removed. assertEquals("Badge index not reused while removing a user", 0, mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id, diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java index 1f4c9f8cd343..b6fd65e5d3b6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java @@ -111,7 +111,7 @@ public class UserManagerServiceIdRecyclingTest { private void removeUser(int userId) { mUserManagerService.removeUserInfo(userId); - mUserManagerService.addRemovingUserIdLocked(userId); + mUserManagerService.addRemovingUserId(userId); } private void assertNoNextIdAvailable(String message) { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 4af0323122ca..592be2d83aee 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -70,6 +70,9 @@ public class UserManagerServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext()); + // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP. + mUserManagerService.putUserInfo( + new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0)); restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml"); restrictionsFile.delete(); 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 ba0743980077..e49b2907c860 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -867,13 +867,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) { + return generateNotificationRecord(channel, 1, userId); + } + + private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, + int userId) { if (channel == null) { channel = mTestNotificationChannel; } Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon); - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0, + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0, nb.build(), new UserHandle(userId), null, 0); return new NotificationRecord(mContext, sbn, channel); } @@ -10734,6 +10739,51 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testUngroupingAutoSummary_differentUsers() throws Exception { + NotificationRecord nr0 = + generateNotificationRecord(mTestNotificationChannel, 0, USER_SYSTEM); + NotificationRecord nr1 = + generateNotificationRecord(mTestNotificationChannel, 1, USER_SYSTEM); + + // add notifications + summary for USER_SYSTEM + mService.addNotification(nr0); + mService.addNotification(nr1); + mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(), + nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS)); + + // add notifications + summary for USER_ALL + NotificationRecord nr0_all = + generateNotificationRecord(mTestNotificationChannel, 2, UserHandle.USER_ALL); + NotificationRecord nr1_all = + generateNotificationRecord(mTestNotificationChannel, 3, UserHandle.USER_ALL); + + mService.addNotification(nr0_all); + mService.addNotification(nr1_all); + mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(), + nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS)); + + // cancel both children for USER_ALL + mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(), + nr0_all.getSbn().getId(), UserHandle.USER_ALL); + mBinderService.cancelNotificationWithTag(PKG, PKG, nr1_all.getSbn().getTag(), + nr1_all.getSbn().getId(), UserHandle.USER_ALL); + waitForIdle(); + + // group helper would send 'remove summary' event + mService.clearAutogroupSummaryLocked(UserHandle.USER_ALL, + nr0_all.getSbn().getPackageName()); + waitForIdle(); + + // make sure the right summary was removed + assertThat(mService.getNotificationCount(nr0_all.getSbn().getPackageName(), + UserHandle.USER_ALL, 0, null)).isEqualTo(0); + + // the USER_SYSTEM notifications + summary were not removed + assertThat(mService.getNotificationCount(nr0.getSbn().getPackageName(), + USER_SYSTEM, 0, null)).isEqualTo(3); + } + + @Test public void testStrongAuthTracker_isInLockDownMode() { mStrongAuthTracker.setGetStrongAuthForUserReturnValue( STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java index 6a1674b7df8e..63b8e1710b50 100644 --- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java @@ -38,13 +38,16 @@ import android.os.BatteryStatsInternal; import android.os.Process; import android.os.RemoteException; +import androidx.test.filters.FlakyTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.FakeLatencyTracker; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; @@ -52,8 +55,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(JUnit4.class) +@FlakyTest(bugId = 275746222) public class SoundTriggerMiddlewareLoggingLatencyTest { + @Rule + public Timeout mGlobalTimeout = Timeout.seconds(30); + private FakeLatencyTracker mLatencyTracker; @Mock private BatteryStatsInternal mBatteryStatsInternal; diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index 280afbadec30..3bb86a7bfecb 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -316,4 +316,16 @@ public class SingleKeyGestureTests { pressKey(KEYCODE_POWER, 0 /* pressTime */); assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); } + + // Verify short press should not be triggered if no very long press behavior defined but the + // press time exceeded the very long press timeout. + @Test + public void testTimeoutExceedVeryLongPress() throws InterruptedException { + mVeryLongPressOnPowerBehavior = false; + + pressKey(KEYCODE_POWER, mVeryLongPressTime + 50); + assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + assertEquals(mVeryLongPressed.getCount(), 1); + assertEquals(mShortPressed.getCount(), 1); + } } 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 5369b93c9534..bdd178b0b317 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2046,6 +2046,7 @@ public class DisplayContentTests extends WindowTestsBase { // Once transition starts, rotation is applied and transition shows DC rotating. testPlayer.startTransition(); + waitUntilHandlersIdle(); assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation()); assertNotNull(testPlayer.mLastReady); assertTrue(testPlayer.mController.isPlaying()); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 204cbf79dba9..994dcf1e2ea5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -31,17 +31,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; -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.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import android.app.StatusBarManager; @@ -268,8 +263,6 @@ public class InsetsPolicyTest extends WindowTestsBase { navBar.setHasSurface(true); navBarProvider.setServerVisible(true); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); // Make both system bars invisible. mAppWindow.setRequestedVisibleTypes( @@ -305,8 +298,6 @@ public class InsetsPolicyTest extends WindowTestsBase { addNavigationBar().getControllableInsetProvider().setServerVisible(true); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); @@ -341,8 +332,6 @@ public class InsetsPolicyTest extends WindowTestsBase { mAppWindow.mAboveInsetsState.addSource(navBarSource); mAppWindow.mAboveInsetsState.addSource(statusBarSource); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); @@ -390,8 +379,6 @@ public class InsetsPolicyTest extends WindowTestsBase { final WindowState app2 = addWindow(TYPE_APPLICATION, "app"); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index 4639ee0ae33d..45ecc3f762ec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -77,6 +77,10 @@ class TestDisplayContent extends DisplayContent { spyOn(inputMonitor); doNothing().when(inputMonitor).resumeDispatchingLw(any()); + final InsetsPolicy insetsPolicy = getInsetsPolicy(); + WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget()); + WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget()); + // For devices that set the sysprop ro.bootanim.set_orientation_<display_id> // See DisplayRotation#readDefaultDisplayRotation for context. // Without that, meaning of height and width in context of the tests can be swapped if diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index adf3f3976f38..bd111ada8550 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -233,7 +233,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { } public void setSafeMode(boolean safeMode) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 197ee92aa7eb..64330d89984e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -81,6 +81,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.util.MergedConfiguration; +import android.view.ContentRecordingSession; import android.view.IWindow; import android.view.IWindowSessionCallback; import android.view.InputChannel; @@ -101,6 +102,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.AdoptShellPermissionsRule; import com.android.internal.os.IResultReceiver; +import com.android.server.LocalServices; import org.junit.Rule; import org.junit.Test; @@ -761,6 +763,63 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test + public void setContentRecordingSession_sessionNull_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + + boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_sessionContentDisplay_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + ContentRecordingSession session = ContentRecordingSession.createDisplaySession( + DEFAULT_DISPLAY); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + IBinder launchCookie = new Binder(); + ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isFalse(); + } + + @Test + public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay)); + ContentRecordingSession session = ContentRecordingSession.createTaskSession( + activityRecord.mLaunchCookie); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + Task task = createTask(mDefaultDisplay); + ActivityRecord activityRecord = createActivityRecord(task); + ContentRecordingSession session = ContentRecordingSession.createTaskSession( + activityRecord.mLaunchCookie); + + wmInternal.setContentRecordingSession(session); + + assertThat(session.getTokenToRecord()).isEqualTo( + task.mRemoteToken.toWindowContainerToken().asBinder()); + } + + @Test public void testisLetterboxBackgroundMultiColored() { assertThat(setupLetterboxConfigurationWithBackgroundType( LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue(); 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 be8ee7832a5d..d5547ec69247 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -222,6 +222,10 @@ class WindowTestsBase extends SystemServiceTestsBase { displayPolicy.finishWindowsDrawn(); displayPolicy.finishScreenTurningOn(); + final InsetsPolicy insetsPolicy = mDefaultDisplay.getInsetsPolicy(); + suppressInsetsAnimation(insetsPolicy.getTransientControlTarget()); + suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget()); + mTransaction = mSystemServicesTestRule.mTransaction; mMockSession = mock(Session.class); @@ -278,6 +282,15 @@ class WindowTestsBase extends SystemServiceTestsBase { checkDeviceSpecificOverridesNotApplied(); } + /** + * The test doesn't create real SurfaceControls, but mocked ones. This prevents the target from + * controlling them, or it will cause {@link NullPointerException}. + */ + static void suppressInsetsAnimation(InsetsControlTarget target) { + spyOn(target); + Mockito.doNothing().when(target).notifyInsetsControlChanged(); + } + @After public void tearDown() throws Exception { if (mUseFakeSettingsProvider) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index dc5b75ad6f14..3e7919305cbf 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -50,7 +50,6 @@ import android.telephony.ims.MediaQualityStatus; import android.telephony.ims.RcsUceAdapter; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.feature.RcsFeature; -import android.telephony.ims.stub.ImsRegistrationImplBase; import com.android.internal.telephony.ICarrierConfigLoader; import com.android.telephony.Rlog; @@ -5813,6 +5812,21 @@ public class CarrierConfigManager { */ public static final int NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED = 3; + /** + * This specifies whether the carrier support the global number format or not. + * {@link SubscriptionManager#getPhoneNumber(int)}, + * {@link SubscriptionManager#getPhoneNumber(int, int)} with + * {@link SubscriptionManager#PHONE_NUMBER_SOURCE_IMS} + * In order to provide the phone number to the APIs, the framework extracts the phone + * number from the message received from the carrier server. If the carrier does not use + * global number format, the framework could not provide phone number. + * <p> + * If not set or set to false value, the framework handle only global number format URI. + * @hide + */ + public static final String KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL = + KEY_PREFIX + "allow_non_global_phone_number_format_bool"; + private Ims() {} private static PersistableBundle getDefaults() { @@ -5925,6 +5939,8 @@ public class CarrierConfigManager { defaults.putString(KEY_IMS_USER_AGENT_STRING, "#MANUFACTURER#_#MODEL#_Android#AV#_#BUILD#"); + defaults.putBoolean(KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, false); + return defaults; } } diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java index db369756c50e..346622f0f467 100644 --- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java +++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java @@ -61,8 +61,11 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { options.setTestMethodName("testCollectAllApexInfo"); // Collect APEX package names from /apex, then pass them as expectation to be verified. + // The package names are collected from the find name with deduplication (NB: we used to + // deduplicate by dropping directory names with '@', but there's a DCLA case where it only + // has one directory with '@'. So we have to keep it and deduplicate the current way). CommandResult result = getDevice().executeShellV2Command( - "ls -d /apex/*/ |grep -v @ |grep -v /apex/sharedlibs |cut -d/ -f3"); + "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq"); assertTrue(result.getStatus() == CommandStatus.SUCCESS); String[] packageNames = result.getStdout().split("\n"); for (var i = 0; i < packageNames.length; i++) { diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 8faedebb4601..37b529bb96c2 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -37,6 +37,7 @@ filegroup { name: "FlickerTestsActivityEmbedding-src", srcs: [ "src/**/activityembedding/*.kt", + "src/**/activityembedding/open/*.kt", "src/**/activityembedding/close/*.kt", "src/**/activityembedding/rotation/*.kt", ], @@ -108,6 +109,7 @@ android_test { ":FlickerTestsAppLaunch-src", ":FlickerTestsQuickswitch-src", ":FlickerTestsRotation-src", + ":FlickerTestsNotification-src", ], } @@ -148,6 +150,9 @@ android_test { ":FlickerTestsBase-src", ":FlickerTestsAppLaunch-src", ], + exclude_srcs: [ + ":FlickerTestsActivityEmbedding-src", + ], } android_test { diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 1ede943a9fa2..ed63ec0a0e83 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -85,6 +85,8 @@ value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/> <option name="directory-keys" value="/data/user/0/com.android.server.wm.flicker.rotation/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.notification/files"/> <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 1fdbe7fd14a9..b18a1a84c61b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -255,20 +255,18 @@ fun LegacyFlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp( invoke("snapshotStartingWindowLayerCoversExactlyOnApp") { val snapshotLayers = it.subjects.filter { subject -> - subject.name.contains(ComponentNameMatcher.SNAPSHOT.toLayerName()) && + ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(subject.layer) && subject.isVisible } + val visibleAreas = + snapshotLayers + .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion } + .toTypedArray() + val snapshotRegion = RegionSubject(visibleAreas, timestamp) // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation. - if (snapshotLayers.isNotEmpty()) { - val visibleAreas = - snapshotLayers - .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion } - .toTypedArray() - val snapshotRegion = RegionSubject(visibleAreas, timestamp) + if (snapshotRegion.region.isNotEmpty) { val appVisibleRegion = it.visibleRegion(component) - if (snapshotRegion.region.isNotEmpty) { - snapshotRegion.coversExactly(appVisibleRegion.region) - } + snapshotRegion.coversExactly(appVisibleRegion.region) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt index 7a582f7de200..4530ef3b88da 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.activityembedding +package com.android.server.wm.flicker.activityembedding.close import android.platform.test.annotations.Presubmit import android.tools.common.datatypes.Rect @@ -24,6 +24,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper import org.junit.FixMethodOrder import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt index 48aaebd14f56..0f406fda28b6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.activityembedding +package com.android.server.wm.flicker.activityembedding.open import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit @@ -24,8 +24,10 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -54,7 +56,7 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest testApp.launchViaIntent(wmHelper) testApp.launchSecondaryActivity(wmHelper) startDisplayBounds = - wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") } transitions { // Launch C with alwaysExpand @@ -66,23 +68,14 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest } } - @FlakyTest(bugId = 286952194) - @Presubmit - @Test - override fun navBarWindowIsVisibleAtStartAndEnd() {} + @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {} - @FlakyTest(bugId = 286952194) - @Presubmit - @Test - override fun statusBarWindowIsAlwaysVisible() {} + @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {} - @FlakyTest(bugId = 286952194) - @Presubmit - @Test - override fun statusBarLayerPositionAtStartAndEnd() {} + @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {} /** Transition begins with a split. */ - @Presubmit + @FlakyTest(bugId = 286952194) @Test fun startsWithSplit() { flicker.assertWmStart { @@ -94,7 +87,7 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest } /** Main activity should become invisible after being covered by always expand activity. */ - @Presubmit + @FlakyTest(bugId = 286952194) @Test fun mainActivityLayerBecomesInvisible() { flicker.assertLayers { @@ -105,7 +98,7 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest } /** Secondary activity should become invisible after being covered by always expand activity. */ - @Presubmit + @FlakyTest(bugId = 286952194) @Test fun secondaryActivityLayerBecomesInvisible() { flicker.assertLayers { @@ -116,7 +109,7 @@ class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest } /** At the end of transition always expand activity is in fullscreen. */ - @Presubmit + @FlakyTest(bugId = 286952194) @Test fun endsWithAlwaysExpandActivityCoveringFullScreen() { flicker.assertWmEnd { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt index 27a5bd0da628..8a997ddb5b35 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.activityembedding +package com.android.server.wm.flicker.activityembedding.open import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -22,6 +22,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper import org.junit.FixMethodOrder import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt index 16dbfce15ab5..49aa84bbffb4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.activityembedding +package com.android.server.wm.flicker.activityembedding.open import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher @@ -23,6 +23,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper import org.junit.FixMethodOrder import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt new file mode 100644 index 000000000000..27de12e7dfdb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.activityembedding.open + +import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launching a secondary activity over an existing split. By default the new secondary activity + * will stack over the previous secondary activity. + * + * Setup: From Activity A launch a split A|B. + * + * Transitions: Let B start C, expect C to cover B and end up in split A|C. + * + * To run this test: `atest FlickerTests:OpenThirdActivityOverSplitTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) : + ActivityEmbeddingTestBase(flicker) { + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + // Launch a split. + testApp.launchViaIntent(wmHelper) + testApp.launchSecondaryActivity(wmHelper) + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds + ?: error("Can't get display bounds") + } + transitions { testApp.launchThirdActivity(wmHelper) } + teardown { + tapl.goHome() + testApp.exit(wmHelper) + } + } + + /** Main activity remains visible throughout the transition. */ + @Presubmit + @Test + fun mainActivityWindowAlwaysVisible() { + flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } + } + + /** Main activity remains visible throughout the transition and takes up half of the screen. */ + @Presubmit + @Test + fun mainActivityLayersAlwaysVisible() { + flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } + + flicker.assertLayersStart { + val display = + this.entry.displays.firstOrNull { it.isOn && !it.isVirtual } + ?: error("No non-virtual and on display found") + val mainActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val secondaryActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT).region + mainActivityRegion.plus(secondaryActivityRegion).coversExactly(display.layerStackSpace) + } + + flicker.assertLayersEnd { + val display = + this.entry.displays.firstOrNull { it.isOn && !it.isVirtual } + ?: error("No non-virtual and on display found") + val mainActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val secondaryActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + secondaryActivityRegion.isEmpty() + val thirdActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + mainActivityRegion + .plus(thirdActivityRegion.region) + .coversExactly(display.layerStackSpace) + } + } + + /** Third activity launches during the transition and covers up secondary activity. */ + @Presubmit + @Test + fun thirdActivityWindowLaunchesIntoSplit() { + flicker.assertWm { + isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isAppWindowInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .isAppWindowInvisible( + ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT + ) // expectation + } + } + + /** Third activity launches during the transition and covers up secondary activity. */ + @Presubmit + @Test + fun thirdActivityLayerLaunchesIntoSplit() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + } + } + + /** Assert the background animation layer is never visible during transition. */ + @Presubmit + @Test + fun backgroundLayerNeverVisible() { + val backgroundColorLayer = ComponentNameMatcher("", "Animation Background") + flicker.assertLayers { isInvisible(backgroundColorLayer) } + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt index 856c9e2abd07..da565001ae10 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.activityembedding +package com.android.server.wm.flicker.activityembedding.rotation import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 865d5b4b771d..dbbc771809de 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt index c1086332a656..566f393efaea 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index ea9710c633b1..ed930fc8c236 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt index d65555a97d78..49ed183c2ccf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index 351eb1e5d2c3..ced7a1ec368c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -59,6 +59,23 @@ constructor( .waitForAndVerify() } + /** Clicks the button to launch a third activity over a secondary activity. */ + fun launchThirdActivity(wmHelper: WindowManagerStateHelper) { + val launchButton = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "launch_third_activity_button")), + FIND_TIMEOUT + ) + require(launchButton != null) { "Can't find launch third activity button on screen." } + launchButton.click() + wmHelper + .StateSyncBuilder() + .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED) + .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .waitForAndVerify() + } + /** * Clicks the button to finishes the secondary activity launched through * [launchSecondaryActivity], waits for the main activity to resume. @@ -166,6 +183,9 @@ constructor( val SECONDARY_ACTIVITY_COMPONENT = ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent() + val THIRD_ACTIVITY_COMPONENT = + ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent() + val ALWAYS_EXPAND_ACTIVITY_COMPONENT = ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt index 24e231c73a0f..c975a50ace02 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt @@ -250,10 +250,13 @@ open class PipAppHelper(instrumentation: Instrumentation) : waitConditions = arrayOf(ConditionsFactory.hasPipWindow()) ) + val windowRegion = wmHelper.getWindowRegion(this) + wmHelper .StateSyncBuilder() .withWindowSurfaceAppeared(this) .withPipShown() + .withSurfaceVisibleRegion(this, windowRegion) .waitForAndVerify() } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt index 2563bfb03f61..4fd4a61e4adc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt index 33302face72a..e39a578fd321 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt index 45fb453f5df8..6d0b6f48d7c6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt index 12ee7d0bff78..d2c38076e72d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt index 1371fd7502d6..3e0958a27aaf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 46eb2571165a..7a16060e3370 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 1497e50641eb..eec6bfde8b9f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -19,7 +19,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt index 9b6c136f54b3..ab6a1ea36222 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt index 4a1bd7e165ae..1bdb6e717b12 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt @@ -18,6 +18,8 @@ package com.android.server.wm.flicker.launch import android.os.SystemClock import android.platform.test.annotations.Postsubmit +import android.tools.common.flicker.subject.layers.LayersTraceSubject +import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.apphelpers.CameraAppHelper import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -137,8 +139,14 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Postsubmit @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + + listOf(CAMERA_BACKGROUND) + ) + } + } @Postsubmit @Test @@ -161,5 +169,12 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + + private val CAMERA_BACKGROUND = + ComponentNameMatcher( + "Background for SurfaceView" + + "[com.google.android.GoogleCamera/" + + "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]" + ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt index c3363995d58f..4e8a697d8902 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt @@ -28,6 +28,7 @@ import android.tools.device.helpers.wakeUpAndGoToHomeScreen import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -106,9 +107,9 @@ class OpenAppFromLockscreenNotificationWithOverlayAppTest(flicker: LegacyFlicker super.visibleLayersShownMoreThanOneConsecutiveEntry() /** {@inheritDoc} */ - @FlakyTest(bugId = 209599395) @Test - override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun navBarLayerIsVisibleAtStartAndEnd() {} /** {@inheritDoc} */ @Presubmit diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 64302831202e..7a2e74bdb8e7 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -200,6 +200,13 @@ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"/> <activity + android:name=".ActivityEmbeddingThirdActivity" + android:label="ActivityEmbedding Third" + android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:exported="false"/> + <activity android:name=".ActivityEmbeddingAlwaysExpandActivity" android:label="ActivityEmbedding AlwaysExpand" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml index 239aba59f4a7..67314463161d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml @@ -27,4 +27,12 @@ android:layout_height="48dp" android:text="Finish" /> + <Button + android:id="@+id/launch_third_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:onClick="launchThirdActivity" + android:text="Launch a third activity" /> + </LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java index 6e78750cdeee..dc21027bc99c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; +import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.View; @@ -40,4 +41,9 @@ public class ActivityEmbeddingSecondaryActivity extends Activity { } }); } + + public void launchThirdActivity(View view) { + startActivity(new Intent().setComponent( + ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT)); + } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java new file mode 100644 index 000000000000..3bd72818d894 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; + +/** + * Activity to be used also as a secondary activity to split with + * {@link ActivityEmbeddingMainActivity}. + */ +public class ActivityEmbeddingThirdActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_embedding_base_layout); + findViewById(R.id.root_activity_layout).setBackgroundColor(Color.RED); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 52106189840d..d84ac427f027 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -99,6 +99,12 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity"); } + public static class ThirdActivity { + public static final String LABEL = "ActivityEmbeddingThirdActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingThirdActivity"); + } + public static class AlwaysExpandActivity { public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp index 90e61c52da68..0d2f2ef46cab 100644 --- a/tests/UiBench/Android.bp +++ b/tests/UiBench/Android.bp @@ -24,6 +24,5 @@ android_test { "androidx.recyclerview_recyclerview", "androidx.leanback_leanback", ], - certificate: "platform", test_suites: ["device-tests"], } diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml index 47211c5fbad1..4fc6ec71f29c 100644 --- a/tests/UiBench/AndroidManifest.xml +++ b/tests/UiBench/AndroidManifest.xml @@ -18,7 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.android.test.uibench"> - <uses-permission android:name="android.permission.INJECT_EVENTS" /> <application android:allowBackup="false" android:theme="@style/Theme.AppCompat.Light.DarkActionBar" diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java index 1b2c3c60ffd4..06b65a7f9bbf 100644 --- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java @@ -15,15 +15,11 @@ */ package com.android.test.uibench; -import android.app.Instrumentation; +import android.content.Intent; import android.os.Bundle; -import android.os.Looper; -import android.os.MessageQueue; -import androidx.appcompat.app.AppCompatActivity; -import android.view.KeyEvent; import android.widget.EditText; -import java.util.concurrent.Semaphore; +import androidx.appcompat.app.AppCompatActivity; /** * Note: currently incomplete, complexity of input continuously grows, instead of looping @@ -32,7 +28,13 @@ import java.util.concurrent.Semaphore; * Simulates typing continuously into an EditText. */ public class EditTextTypeActivity extends AppCompatActivity { - Thread mThread; + + /** + * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the + * test activity was paused. + */ + private static final String ACTION_CANCEL_TYPING_CALLBACK = + "com.android.uibench.action.CANCEL_TYPING_CALLBACK"; private static String sSeedText = ""; static { @@ -46,9 +48,6 @@ public class EditTextTypeActivity extends AppCompatActivity { sSeedText = builder.toString(); } - final Object mLock = new Object(); - boolean mShouldStop = false; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,55 +55,13 @@ public class EditTextTypeActivity extends AppCompatActivity { EditText editText = new EditText(this); editText.setText(sSeedText); setContentView(editText); - - final Instrumentation instrumentation = new Instrumentation(); - final Semaphore sem = new Semaphore(0); - MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() { - @Override - public boolean queueIdle() { - // TODO: consider other signaling approaches - sem.release(); - return true; - } - }; - Looper.myQueue().addIdleHandler(handler); - synchronized (mLock) { - mShouldStop = false; - } - mThread = new Thread(new Runnable() { - int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L, - KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE }; - int i = 0; - @Override - public void run() { - while (true) { - try { - sem.acquire(); - } catch (InterruptedException e) { - // TODO, maybe - } - int code = codes[i % codes.length]; - if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER; - - synchronized (mLock) { - if (mShouldStop) break; - } - - // TODO: bit of a race here, since the event can arrive after pause/stop. - // (Can't synchronize on key send, since it's synchronous.) - instrumentation.sendKeyDownUpSync(code); - i++; - } - } - }); - mThread.start(); } @Override protected void onPause() { - synchronized (mLock) { - mShouldStop = true; - } + // Cancel the typing when the test activity was paused. + sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags( + Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY)); super.onPause(); } } |