diff options
202 files changed, 7026 insertions, 1966 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 9851428cf283..de540bf93e4b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4757,6 +4757,7 @@ package android.app { method public int getLaunchDisplayId(); method public boolean getLockTaskMode(); method public int getPendingIntentBackgroundActivityStartMode(); + method public int getPendingIntentCreatorBackgroundActivityStartMode(); method public int getSplashScreenStyle(); method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed(); method public boolean isShareIdentityEnabled(); @@ -4777,6 +4778,7 @@ package android.app { method public android.app.ActivityOptions setLockTaskEnabled(boolean); method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean); method @NonNull public android.app.ActivityOptions setPendingIntentBackgroundActivityStartMode(int); + method @NonNull public android.app.ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(int); method @NonNull public android.app.ActivityOptions setShareIdentityEnabled(boolean); method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int); method public android.os.Bundle toBundle(); @@ -24030,6 +24032,7 @@ package android.media { method @Nullable public android.net.Uri getIconUri(); method @NonNull public String getId(); method @NonNull public CharSequence getName(); + method public int getType(); method public int getVolume(); method public int getVolumeHandling(); method public int getVolumeMax(); @@ -24046,6 +24049,22 @@ package android.media { field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK"; field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0 field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1 + field public static final int TYPE_BLE_HEADSET = 26; // 0x1a + field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8 + field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2 + field public static final int TYPE_DOCK = 13; // 0xd + field public static final int TYPE_GROUP = 2000; // 0x7d0 + field public static final int TYPE_HDMI = 9; // 0x9 + field public static final int TYPE_HEARING_AID = 23; // 0x17 + field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb + field public static final int TYPE_REMOTE_SPEAKER = 1002; // 0x3ea + field public static final int TYPE_REMOTE_TV = 1001; // 0x3e9 + field public static final int TYPE_UNKNOWN = 0; // 0x0 + field public static final int TYPE_USB_ACCESSORY = 12; // 0xc + field public static final int TYPE_USB_DEVICE = 11; // 0xb + field public static final int TYPE_USB_HEADSET = 22; // 0x16 + field public static final int TYPE_WIRED_HEADPHONES = 4; // 0x4 + field public static final int TYPE_WIRED_HEADSET = 3; // 0x3 } public static final class MediaRoute2Info.Builder { @@ -24061,6 +24080,7 @@ package android.media { method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence); method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle); method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri); + method @NonNull public android.media.MediaRoute2Info.Builder setType(int); method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic(); method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>); method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int); @@ -24634,7 +24654,7 @@ package android.media { field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR; field public static final int FLAG_ONGOING_SESSION = 1; // 0x1 field public static final int FLAG_ONGOING_SESSION_MANAGED = 2; // 0x2 - field public static final int FLAG_SUGGESTED_ROUTE = 4; // 0x4 + field public static final int FLAG_SUGGESTED = 4; // 0x4 field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2 field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0 field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1 @@ -26328,8 +26348,23 @@ package android.media.session { package android.media.tv { + public final class AdBuffer implements android.os.Parcelable { + ctor public AdBuffer(int, @NonNull String, @NonNull android.os.SharedMemory, int, int, long, int); + method public int describeContents(); + method public int getFlags(); + method public int getId(); + method public int getLength(); + method @NonNull public String getMimeType(); + method public int getOffset(); + method public long getPresentationTimeUs(); + method @NonNull public android.os.SharedMemory getSharedMemory(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdBuffer> CREATOR; + } + public final class AdRequest implements android.os.Parcelable { ctor public AdRequest(int, int, @Nullable android.os.ParcelFileDescriptor, long, long, long, @Nullable String, @NonNull android.os.Bundle); + ctor public AdRequest(int, int, @Nullable android.net.Uri, long, long, long, @NonNull android.os.Bundle); method public int describeContents(); method public long getEchoIntervalMillis(); method @Nullable public android.os.ParcelFileDescriptor getFileDescriptor(); @@ -26339,6 +26374,7 @@ package android.media.tv { method public int getRequestType(); method public long getStartTimeMillis(); method public long getStopTimeMillis(); + method @Nullable public android.net.Uri getUri(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdRequest> CREATOR; field public static final int REQUEST_TYPE_START = 1; // 0x1 @@ -26353,6 +26389,7 @@ package android.media.tv { method public int getResponseType(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AdResponse> CREATOR; + field public static final int RESPONSE_TYPE_BUFFERING = 5; // 0x5 field public static final int RESPONSE_TYPE_ERROR = 4; // 0x4 field public static final int RESPONSE_TYPE_FINISHED = 2; // 0x2 field public static final int RESPONSE_TYPE_PLAYING = 1; // 0x1 @@ -27106,6 +27143,7 @@ package android.media.tv { public abstract static class TvInputService.Session implements android.view.KeyEvent.Callback { ctor public TvInputService.Session(android.content.Context); method public void layoutSurface(int, int, int, int); + method public void notifyAdBufferConsumed(@NonNull android.media.tv.AdBuffer); method public void notifyAdResponse(@NonNull android.media.tv.AdResponse); method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo); method public void notifyAudioPresentationChanged(@NonNull java.util.List<android.media.AudioPresentation>); @@ -27121,6 +27159,7 @@ package android.media.tv { method public void notifyTuned(@NonNull android.net.Uri); method public void notifyVideoAvailable(); method public void notifyVideoUnavailable(int); + method public void onAdBuffer(@NonNull android.media.tv.AdBuffer); method public void onAppPrivateCommand(@NonNull String, android.os.Bundle); method public android.view.View onCreateOverlayView(); method public boolean onGenericMotionEvent(android.view.MotionEvent); @@ -27407,9 +27446,11 @@ package android.media.tv.interactive { ctor public TvInteractiveAppService.Session(@NonNull android.content.Context); method public boolean isMediaViewEnabled(); method @CallSuper public void layoutSurface(int, int, int, int); + method @CallSuper public void notifyAdBuffer(@NonNull android.media.tv.AdBuffer); method @CallSuper public final void notifyBiInteractiveAppCreated(@NonNull android.net.Uri, @Nullable String); method @CallSuper public void notifySessionStateChanged(int, int); method @CallSuper public final void notifyTeletextAppStateChanged(int); + method public void onAdBufferConsumed(@NonNull android.media.tv.AdBuffer); method public void onAdResponse(@NonNull android.media.tv.AdResponse); method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse); method public void onContentAllowed(); @@ -45372,6 +45413,7 @@ package android.telephony { field public static final String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS"; field @Deprecated public static final String EXTRA_INCOMING_NUMBER = "incoming_number"; field public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH"; + field public static final String EXTRA_LAST_KNOWN_NETWORK_COUNTRY = "android.telephony.extra.LAST_KNOWN_NETWORK_COUNTRY"; field public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT"; field public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY"; field public static final String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d0c000e4d4b1..4a0b2eb502dd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9783,6 +9783,133 @@ package android.net.wifi.nl80211 { } +package android.net.wifi.sharedconnectivity.app { + + public final class DeviceInfo implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=100) @NonNull public int getBatteryPercentage(); + method @IntRange(from=0, to=3) @NonNull public int getConnectionStrength(); + method @NonNull public String getDeviceName(); + method public int getDeviceType(); + method @NonNull public String getModelName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.DeviceInfo> CREATOR; + field public static final int DEVICE_TYPE_LAPTOP = 3; // 0x3 + field public static final int DEVICE_TYPE_PHONE = 1; // 0x1 + field public static final int DEVICE_TYPE_TABLET = 2; // 0x2 + field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0 + } + + public static final class DeviceInfo.Builder { + ctor public DeviceInfo.Builder(); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo build(); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setDeviceName(@NonNull String); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setDeviceType(int); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo.Builder setModelName(@NonNull String); + } + + public final class KnownNetwork implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo(); + method @NonNull public int getNetworkSource(); + method @NonNull public int[] getSecurityTypes(); + method @NonNull public String getSsid(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.KnownNetwork> CREATOR; + field public static final int NETWORK_SOURCE_CLOUD_SELF = 1; // 0x1 + field public static final int NETWORK_SOURCE_NEARBY_SELF = 0; // 0x0 + } + + public static final class KnownNetwork.Builder { + ctor public KnownNetwork.Builder(); + method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build(); + method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo); + method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int); + method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSecurityTypes(@NonNull int[]); + method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String); + } + + public interface SharedConnectivityClientCallback { + method public void onKnownNetworksUpdated(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork>); + method public void onSharedConnectivitySettingsChanged(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState); + method public void onTetherNetworksUpdated(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.TetherNetwork>); + } + + public class SharedConnectivityManager { + ctor public SharedConnectivityManager(@NonNull android.content.Context); + method public boolean connectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); + method public boolean connectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork); + method public boolean disconnectTetherNetwork(); + method public boolean forgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); + method public boolean registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback); + method public boolean unregisterCallback(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback); + } + + public final class SharedConnectivitySettingsState implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public boolean isInstantTetherEnabled(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState> CREATOR; + } + + public static final class SharedConnectivitySettingsState.Builder { + ctor public SharedConnectivitySettingsState.Builder(); + method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState build(); + method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState.Builder setInstantTetherEnabled(boolean); + } + + public final class TetherNetwork implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public long getDeviceId(); + method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo(); + method @Nullable public String getHotspotBssid(); + method @Nullable public int[] getHotspotSecurityTypes(); + method @Nullable public String getHotspotSsid(); + method @NonNull public String getNetworkName(); + method @NonNull public int getNetworkType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.TetherNetwork> CREATOR; + field public static final int NETWORK_TYPE_CELLULAR = 1; // 0x1 + field public static final int NETWORK_TYPE_ETHERNET = 3; // 0x3 + field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0 + field public static final int NETWORK_TYPE_WIFI = 2; // 0x2 + } + + public static final class TetherNetwork.Builder { + ctor public TetherNetwork.Builder(); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork build(); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceId(long); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotBssid(@NonNull String); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSecurityTypes(@NonNull int[]); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSsid(@NonNull String); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkName(@NonNull String); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkType(int); + } + +} + +package android.net.wifi.sharedconnectivity.service { + + public abstract class SharedConnectivityService extends android.app.Service { + ctor public SharedConnectivityService(); + ctor public SharedConnectivityService(@NonNull android.os.Handler); + method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onConnectKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); + method public abstract void onConnectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork); + method public abstract void onDisconnectTetherNetwork(); + method public abstract void onForgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); + method public final void setKnownNetworks(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork>); + method public final void setSettingsState(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState); + method public final void setTetherNetworks(@NonNull java.util.List<android.net.wifi.sharedconnectivity.app.TetherNetwork>); + } + +} + package android.nfc { public final class NfcAdapter { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2f5e8201c8d1..b3e3f35452ce 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1863,6 +1863,14 @@ package android.net { } +package android.net.wifi.sharedconnectivity.app { + + public class SharedConnectivityManager { + method public void setService(@Nullable android.os.IInterface); + } + +} + package android.os { public final class BatteryStatsManager { diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index b57fb2088adc..3b882571507f 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -343,6 +343,13 @@ public class ActivityOptions extends ComponentOptions { "android:activity.applyActivityFlagsForBubbles"; /** + * Indicates to apply {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the launching shortcut. + * @hide + */ + private static final String KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT = + "android:activity.applyMultipleTaskFlagForShortcut"; + + /** * For Activity transitions, the calling Activity's TransitionListener used to * notify the called Activity when the shared element and the exit transitions * complete. @@ -397,8 +404,8 @@ public class ActivityOptions extends ComponentOptions { /** See {@link #setDismissKeyguard()}. */ private static final String KEY_DISMISS_KEYGUARD = "android.activity.dismissKeyguard"; - private static final String KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE = - "android.activity.ignorePendingIntentCreatorForegroundState"; + private static final String KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE = + "android.activity.pendingIntentCreatorBackgroundActivityStartMode"; /** * @see #setLaunchCookie @@ -476,6 +483,7 @@ public class ActivityOptions extends ComponentOptions { private boolean mShareIdentity = false; private boolean mDisallowEnterPictureInPictureWhileLaunching; private boolean mApplyActivityFlagsForBubbles; + private boolean mApplyMultipleTaskFlagForShortcut; private boolean mTaskAlwaysOnTop; private boolean mTaskOverlay; private boolean mTaskOverlayCanResume; @@ -499,7 +507,9 @@ public class ActivityOptions extends ComponentOptions { private boolean mTransientLaunch; private PictureInPictureParams mLaunchIntoPipParams; private boolean mDismissKeyguard; - private boolean mIgnorePendingIntentCreatorForegroundState; + @BackgroundActivityStartMode + private int mPendingIntentCreatorBackgroundActivityStartMode = + MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; private boolean mDisableStartingWindow; /** @@ -1276,6 +1286,8 @@ public class ActivityOptions extends ComponentOptions { KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false); mApplyActivityFlagsForBubbles = opts.getBoolean( KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, false); + mApplyMultipleTaskFlagForShortcut = opts.getBoolean( + KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT, false); if (opts.containsKey(KEY_ANIM_SPECS)) { Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS); mAnimSpecs = new AppTransitionAnimationSpec[specs.length]; @@ -1307,8 +1319,9 @@ public class ActivityOptions extends ComponentOptions { mIsEligibleForLegacyPermissionPrompt = opts.getBoolean(KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE); mDismissKeyguard = opts.getBoolean(KEY_DISMISS_KEYGUARD); - mIgnorePendingIntentCreatorForegroundState = opts.getBoolean( - KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE); + mPendingIntentCreatorBackgroundActivityStartMode = opts.getInt( + KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE, + MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW); } @@ -1903,6 +1916,16 @@ public class ActivityOptions extends ComponentOptions { return mApplyActivityFlagsForBubbles; } + /** @hide */ + public void setApplyMultipleTaskFlagForShortcut(boolean apply) { + mApplyMultipleTaskFlagForShortcut = apply; + } + + /** @hide */ + public boolean isApplyMultipleTaskFlagForShortcut() { + return mApplyMultipleTaskFlagForShortcut; + } + /** * Sets a launch cookie that can be used to track the activity and task that are launch as a * result of this option. If the launched activity is a trampoline that starts another activity @@ -2009,19 +2032,38 @@ public class ActivityOptions extends ComponentOptions { * Sets background activity launch logic won't use pending intent creator foreground state. * * @hide + * @deprecated use {@link #setPendingIntentCreatorBackgroundActivityStartMode(int)} instead */ - public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean state) { - mIgnorePendingIntentCreatorForegroundState = state; + @Deprecated + public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean ignore) { + mPendingIntentCreatorBackgroundActivityStartMode = ignore + ? MODE_BACKGROUND_ACTIVITY_START_DENIED : MODE_BACKGROUND_ACTIVITY_START_ALLOWED; return this; } /** - * @return whether background activity launch logic should use pending intent creator - * foreground state. - * @hide + * Allow a {@link PendingIntent} to use the privilege of its creator to start background + * activities. + * + * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set + * @throws IllegalArgumentException is the value is not a valid + * {@link android.app.ComponentOptions.BackgroundActivityStartMode} + */ + @NonNull + public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode( + @BackgroundActivityStartMode int mode) { + mPendingIntentCreatorBackgroundActivityStartMode = mode; + return this; + } + + /** + * Returns the mode to start background activities granted by the creator of the + * {@link PendingIntent}. + * + * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set */ - public boolean getIgnorePendingIntentCreatorForegroundState() { - return mIgnorePendingIntentCreatorForegroundState; + public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() { + return mPendingIntentCreatorBackgroundActivityStartMode; } /** @@ -2240,6 +2282,10 @@ public class ActivityOptions extends ComponentOptions { if (mApplyActivityFlagsForBubbles) { b.putBoolean(KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, mApplyActivityFlagsForBubbles); } + if (mApplyMultipleTaskFlagForShortcut) { + b.putBoolean(KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT, + mApplyMultipleTaskFlagForShortcut); + } if (mAnimSpecs != null) { b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs); } @@ -2295,9 +2341,10 @@ public class ActivityOptions extends ComponentOptions { if (mDismissKeyguard) { b.putBoolean(KEY_DISMISS_KEYGUARD, mDismissKeyguard); } - if (mIgnorePendingIntentCreatorForegroundState) { - b.putBoolean(KEY_IGNORE_PENDING_INTENT_CREATOR_FOREGROUND_STATE, - mIgnorePendingIntentCreatorForegroundState); + if (mPendingIntentCreatorBackgroundActivityStartMode + != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + b.putInt(KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE, + mPendingIntentCreatorBackgroundActivityStartMode); } if (mDisableStartingWindow) { b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 73f34ebd8bc5..7d40a2299a80 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1432,14 +1432,14 @@ public class AppOpsManager { .APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION; /** - * Hide foreground service stop button in quick settings. + * Allows an application to start an activity while running in the background. * * Only to be used by the system. * * @hide */ - public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON = - AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON; + public static final int OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION = + AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION; /** * Allows an application to capture bugreport directly without consent dialog when using the @@ -2027,14 +2027,14 @@ public class AppOpsManager { "android:system_exempt_from_fgs_bg_start_while_in_use_permission_restriction"; /** - * Hide foreground service stop button in quick settings. + * Allows an application to start an activity while running in the background. * * Only to be used by the system. * * @hide */ - public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON = - "android:system_exempt_from_fgs_stop_button"; + public static final String OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION = + "android:system_exempt_from_activity_bg_start_restriction"; /** * Allows an application to capture bugreport directly without consent dialog when using the @@ -2562,9 +2562,9 @@ public class AppOpsManager { OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION, "SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION") .build(), - new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON, - OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON, - "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build(), + new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, + OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, + "SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION").build(), new AppOpInfo.Builder( OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index 20d19c16f8d1..5ef29e4e0b5b 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -388,7 +388,6 @@ public abstract class ForegroundServiceTypePolicy { new RegularPermission(Manifest.permission.SCHEDULE_EXACT_ALARM), new RegularPermission(Manifest.permission.USE_EXACT_ALARM), new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN), - new AppOpPermission(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), }, false) ); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index d8ec7cc10159..b494a2002689 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -39,6 +39,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.StringDef; +import android.annotation.SupportsCoexistence; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -8596,6 +8597,7 @@ public class DevicePolicyManager { * primary user, or a profile owner of an organization-owned managed profile or a holder of the * permission {@link android.Manifest.permission#SET_TIME_ZONE}. */ + @SupportsCoexistence @RequiresPermission(value = SET_TIME_ZONE, conditional = true) public void setAutoTimeZoneEnabled(@NonNull ComponentName admin, boolean enabled) { throwIfParentInstance("setAutoTimeZone"); @@ -9665,6 +9667,7 @@ public class DevicePolicyManager { * @param activity The Activity that is added as default intent handler. * @throws SecurityException if {@code admin} is not a device or profile owner. */ + @SupportsCoexistence public void addPersistentPreferredActivity(@NonNull ComponentName admin, IntentFilter filter, @NonNull ComponentName activity) { throwIfParentInstance("addPersistentPreferredActivity"); @@ -10624,6 +10627,7 @@ public class DevicePolicyManager { * profile owner of an organization-owned managed profile and the * list of permitted input method package names is not null or empty. */ + @SupportsCoexistence public boolean setPermittedInputMethods( @NonNull ComponentName admin, List<String> packageNames) { if (mService != null) { @@ -11696,6 +11700,7 @@ public class DevicePolicyManager { * @see DeviceAdminReceiver#onLockTaskModeExiting(Context, Intent) * @see UserManager#DISALLOW_CREATE_WINDOWS */ + @SupportsCoexistence public void setLockTaskPackages(@NonNull ComponentName admin, @NonNull String[] packages) throws SecurityException { throwIfParentInstance("setLockTaskPackages"); @@ -11764,6 +11769,7 @@ public class DevicePolicyManager { * affiliated user or profile, or the profile owner when no device owner is set. * @see #isAffiliatedUser **/ + @SupportsCoexistence public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) { throwIfParentInstance("setLockTaskFeatures"); if (mService != null) { @@ -12267,6 +12273,7 @@ public class DevicePolicyManager { * @see #setDelegatedScopes * @see #DELEGATION_BLOCK_UNINSTALL */ + @SupportsCoexistence public void setUninstallBlocked(@Nullable ComponentName admin, String packageName, boolean uninstallBlocked) { throwIfParentInstance("setUninstallBlocked"); @@ -12755,6 +12762,7 @@ public class DevicePolicyManager { * @see #setDelegatedScopes * @see #DELEGATION_PERMISSION_GRANT */ + @SupportsCoexistence public boolean setPermissionGrantState(@NonNull ComponentName admin, @NonNull String packageName, @NonNull String permission, @PermissionGrantState int grantState) { @@ -15301,6 +15309,7 @@ public class DevicePolicyManager { * @param packages The package names for the apps. * @throws SecurityException if {@code admin} is not a device owner or a profile owner. */ + @SupportsCoexistence public void setUserControlDisabledPackages(@NonNull ComponentName admin, @NonNull List<String> packages) { throwIfParentInstance("setUserControlDisabledPackages"); diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 6e49e956fe7e..b7ec7b55d7db 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -545,6 +545,7 @@ public class AssistStructure implements Parcelable { int resolveViewAutofillFlags(Context context, int fillRequestFlags) { return (fillRequestFlags & FillRequest.FLAG_MANUAL_REQUEST) != 0 || context.isAutofillCompatibilityEnabled() + || (fillRequestFlags & FillRequest.FLAG_PCC_DETECTION) != 0 ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0; } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 335975bae393..451566365d0c 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1107,7 +1107,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration } else { sb.append("?mcc"); } - if (mnc != 0) { + if (mnc != MNC_ZERO) { sb.append(mnc); sb.append("mnc"); } else { diff --git a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl index 8587348ef38b..7844b40c71a6 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl @@ -39,5 +39,15 @@ oneway interface IUdfpsRefreshRateRequestCallback { * {@link android.view.Display#getDisplayId()}. */ void onRequestDisabled(int displayId); + + /** + * To avoid delay in switching refresh rate when activating LHBM, allow screens to request + * higher refersh rate if auth is possible on particular screen + * + * @param displayId The displayId for which the refresh rate should be unset. See + * {@link android.view.Display#getDisplayId()}. + * @param isPossible If authentication is possible on particualr screen + */ + void onAuthenticationPossible(int displayId, boolean isPossible); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 0efd264335a2..1df45d132f34 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1069,11 +1069,6 @@ public class UserManager { * Specifies that windows besides app windows should not be * created. This will block the creation of the following types of windows. * <li>{@link LayoutParams#TYPE_TOAST}</li> - * <li>{@link LayoutParams#TYPE_PHONE}</li> - * <li>{@link LayoutParams#TYPE_PRIORITY_PHONE}</li> - * <li>{@link LayoutParams#TYPE_SYSTEM_ALERT}</li> - * <li>{@link LayoutParams#TYPE_SYSTEM_ERROR}</li> - * <li>{@link LayoutParams#TYPE_SYSTEM_OVERLAY}</li> * <li>{@link LayoutParams#TYPE_APPLICATION_OVERLAY}</li> * * <p>This can only be set by device owners and profile owners on the primary user. diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS index c80c57ce917a..e5b76f6c1c15 100644 --- a/core/java/android/os/storage/OWNERS +++ b/core/java/android/os/storage/OWNERS @@ -1,7 +1,9 @@ # Bug component: 95221 +# Please assign new bugs to android-storage-triage@, not to individual people + # Android Storage Team -abkaur@google.com +alukin@google.com corinac@google.com dipankarb@google.com krishang@google.com diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1c032ee5966f..b29efab06d1b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11468,6 +11468,13 @@ public final class Settings { "extra_automatic_power_save_mode"; /** + * Whether lockscreen weather is enabled. + * + * @hide + */ + public static final String LOCK_SCREEN_WEATHER_ENABLED = "lockscreen_weather_enabled"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index 0f7c9b63dac0..eb5e893416c7 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -117,6 +117,12 @@ public final class FillRequest implements Parcelable { */ public static final @RequestFlags int FLAG_RESET_FILL_DIALOG_STATE = 0x100; + /** + * Indicate the fill request is made for PCC detection + * @hide + */ + public static final @RequestFlags int FLAG_PCC_DETECTION = 0x200; + /** @hide */ public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE; @@ -200,7 +206,7 @@ public final class FillRequest implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/FillRequest.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -215,7 +221,8 @@ public final class FillRequest implements Parcelable { FLAG_VIEW_NOT_FOCUSED, FLAG_SUPPORTS_FILL_DIALOG, FLAG_IME_SHOWING, - FLAG_RESET_FILL_DIALOG_STATE + FLAG_RESET_FILL_DIALOG_STATE, + FLAG_PCC_DETECTION }) @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member @@ -245,6 +252,8 @@ public final class FillRequest implements Parcelable { return "FLAG_IME_SHOWING"; case FLAG_RESET_FILL_DIALOG_STATE: return "FLAG_RESET_FILL_DIALOG_STATE"; + case FLAG_PCC_DETECTION: + return "FLAG_PCC_DETECTION"; default: return Integer.toHexString(value); } } @@ -322,7 +331,8 @@ public final class FillRequest implements Parcelable { | FLAG_VIEW_NOT_FOCUSED | FLAG_SUPPORTS_FILL_DIALOG | FLAG_IME_SHOWING - | FLAG_RESET_FILL_DIALOG_STATE); + | FLAG_RESET_FILL_DIALOG_STATE + | FLAG_PCC_DETECTION); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; this.mDelayedFillIntentSender = delayedFillIntentSender; @@ -463,7 +473,7 @@ public final class FillRequest implements Parcelable { byte flg = in.readByte(); int id = in.readInt(); List<FillContext> fillContexts = new ArrayList<>(); - in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class); + in.readParcelableList(fillContexts, FillContext.class.getClassLoader()); Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle(); int flags = in.readInt(); InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR); @@ -484,7 +494,8 @@ public final class FillRequest implements Parcelable { | FLAG_VIEW_NOT_FOCUSED | FLAG_SUPPORTS_FILL_DIALOG | FLAG_IME_SHOWING - | FLAG_RESET_FILL_DIALOG_STATE); + | FLAG_RESET_FILL_DIALOG_STATE + | FLAG_PCC_DETECTION); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; this.mDelayedFillIntentSender = delayedFillIntentSender; @@ -506,10 +517,10 @@ public final class FillRequest implements Parcelable { }; @DataClass.Generated( - time = 1663290803064L, + time = 1675460688829L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java", - inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 70a773994926..ba7d823750a3 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -781,19 +781,6 @@ public class InsetsState implements Parcelable { } } - public static boolean containsType(@InternalInsetsType int[] types, - @InternalInsetsType int type) { - if (types == null) { - return false; - } - for (int t : types) { - if (t == type) { - return true; - } - } - return false; - } - public void dump(String prefix, PrintWriter pw) { final String newPrefix = prefix + " "; pw.println(prefix + "InsetsState"); diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 7d1dc7660871..c8c910d98bbb 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -28,6 +28,7 @@ import android.content.res.XmlResourceParser; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Xml; +import android.view.InflateException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -137,16 +138,9 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createAnimationFromXml(context, parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException | InflateException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { if (parser != null) parser.close(); } @@ -159,8 +153,9 @@ public class AnimationUtils { } @UnsupportedAppUsage - private static Animation createAnimationFromXml(Context c, XmlPullParser parser, - AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException { + private static Animation createAnimationFromXml( + Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs) + throws XmlPullParserException, IOException, InflateException { Animation anim = null; @@ -168,8 +163,8 @@ public class AnimationUtils { int type; int depth = parser.getDepth(); - while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) - && type != XmlPullParser.END_DOCUMENT) { + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; @@ -193,7 +188,7 @@ public class AnimationUtils { } else if (name.equals("extend")) { anim = new ExtendAnimation(c, attrs); } else { - throw new RuntimeException("Unknown animation name: " + parser.getName()); + throw new InflateException("Unknown animation name: " + parser.getName()); } if (parent != null) { @@ -220,29 +215,24 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createLayoutAnimationFromXml(context, parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException | InflateException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { if (parser != null) parser.close(); } } - private static LayoutAnimationController createLayoutAnimationFromXml(Context c, - XmlPullParser parser) throws XmlPullParserException, IOException { + private static LayoutAnimationController createLayoutAnimationFromXml( + Context c, XmlPullParser parser) + throws XmlPullParserException, IOException, InflateException { return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser)); } - private static LayoutAnimationController createLayoutAnimationFromXml(Context c, - XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { + private static LayoutAnimationController createLayoutAnimationFromXml( + Context c, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException, InflateException { LayoutAnimationController controller = null; @@ -263,7 +253,7 @@ public class AnimationUtils { } else if ("gridLayoutAnimation".equals(name)) { controller = new GridLayoutAnimationController(c, attrs); } else { - throw new RuntimeException("Unknown layout animation name: " + name); + throw new InflateException("Unknown layout animation name: " + name); } } @@ -342,16 +332,9 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException | InflateException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { if (parser != null) parser.close(); } @@ -372,25 +355,20 @@ public class AnimationUtils { try { parser = res.getAnimation(id); return createInterpolatorFromXml(res, theme, parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException | InflateException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { - if (parser != null) + if (parser != null) { parser.close(); + } } } - private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser) - throws XmlPullParserException, IOException { + private static Interpolator createInterpolatorFromXml( + Resources res, Theme theme, XmlPullParser parser) + throws XmlPullParserException, IOException, InflateException { BaseInterpolator interpolator = null; @@ -430,7 +408,7 @@ public class AnimationUtils { } else if (name.equals("pathInterpolator")) { interpolator = new PathInterpolator(res, theme, attrs); } else { - throw new RuntimeException("Unknown interpolator name: " + parser.getName()); + throw new InflateException("Unknown interpolator name: " + parser.getName()); } } return interpolator; diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 681bc7a0a03f..1eecb413adcb 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -202,12 +202,12 @@ public class ChooserListAdapter extends ResolverListAdapter { ri.nonLocalizedLabel = li.getNonLocalizedLabel(); ri.icon = li.getIconResource(); ri.iconResourceId = ri.icon; - ri.userHandle = mInitialIntentsUserSpace; } if (userManager.isManagedProfile()) { ri.noResourceId = true; ri.icon = 0; } + ri.userHandle = mInitialIntentsUserSpace; mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri))); if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break; } diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 0ea60a7ddbfe..18c8eb4ec46b 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -447,13 +447,13 @@ public class ResolverListAdapter extends BaseAdapter { ri.nonLocalizedLabel = li.getNonLocalizedLabel(); ri.icon = li.getIconResource(); ri.iconResourceId = ri.icon; - ri.userHandle = mInitialIntentsUserSpace; } if (userManager.isManagedProfile()) { ri.noResourceId = true; ri.icon = 0; } + ri.userHandle = mInitialIntentsUserSpace; addResolveInfo(new DisplayResolveInfo(ii, ri, ri.loadLabel(mPm), null, ii, makePresentationGetter(ri))); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 1c85ca2bb269..b529a1016464 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -214,11 +214,11 @@ oneway interface IStatusBar * bar and navigation bar which are temporarily visible to the user. * * @param displayId the ID of the display to notify. - * @param types the internal insets types of the bars are about to show transiently. + * @param types the insets types of the bars are about to show transiently. * @param isGestureOnSystemBar whether the gesture to show the transient bar was a gesture on * one of the bars itself. */ - void showTransient(int displayId, in int[] types, boolean isGestureOnSystemBar); + void showTransient(int displayId, int types, boolean isGestureOnSystemBar); /** * Notifies System UI to abort the transient state of system bars, which prevents the bars being @@ -226,9 +226,9 @@ oneway interface IStatusBar * bars again. * * @param displayId the ID of the display to notify. - * @param types the internal insets types of the bars are about to abort the transient state. + * @param types the insets types of the bars are about to abort the transient state. */ - void abortTransient(int displayId, in int[] types); + void abortTransient(int displayId, int types); /** * Show a warning that the device is about to go to sleep due to user inactivity. diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java index 54221ce92dc4..4f827cda6afa 100644 --- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java +++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java @@ -16,7 +16,6 @@ package com.android.internal.statusbar; -import android.annotation.NonNull; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -41,15 +40,14 @@ public final class RegisterStatusBarResult implements Parcelable { public final int mBehavior; public final int mRequestedVisibleTypes; public final String mPackageName; - public final int[] mTransientBarTypes; + public final int mTransientBarTypes; public final LetterboxDetails[] mLetterboxDetails; public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1, int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis, int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken, boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes, - String packageName, @NonNull int[] transientBarTypes, - LetterboxDetails[] letterboxDetails) { + String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) { mIcons = new ArrayMap<>(icons); mDisabledFlags1 = disabledFlags1; mAppearance = appearance; @@ -87,7 +85,7 @@ public final class RegisterStatusBarResult implements Parcelable { dest.writeInt(mBehavior); dest.writeInt(mRequestedVisibleTypes); dest.writeString(mPackageName); - dest.writeIntArray(mTransientBarTypes); + dest.writeInt(mTransientBarTypes); dest.writeParcelableArray(mLetterboxDetails, flags); } @@ -113,7 +111,7 @@ public final class RegisterStatusBarResult implements Parcelable { final int behavior = source.readInt(); final int requestedVisibleTypes = source.readInt(); final String packageName = source.readString(); - final int[] transientBarTypes = source.createIntArray(); + final int transientBarTypes = source.readInt(); final LetterboxDetails[] letterboxDetails = source.readParcelableArray(null, LetterboxDetails.class); return new RegisterStatusBarResult(icons, disabledFlags1, appearance, diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java index e56c381ead33..5c3759f96764 100644 --- a/core/java/com/android/internal/widget/LockPatternChecker.java +++ b/core/java/com/android/internal/widget/LockPatternChecker.java @@ -1,10 +1,7 @@ package com.android.internal.widget; -import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION; - import android.annotation.NonNull; import android.os.AsyncTask; -import android.provider.DeviceConfig; import com.android.internal.widget.LockPatternUtils.RequestThrottledException; @@ -120,8 +117,7 @@ public final class LockPatternChecker { @Override protected void onPostExecute(Boolean result) { callback.onChecked(result, mThrottleTimeout); - if (DeviceConfig.getBoolean(NAMESPACE_AUTO_PIN_CONFIRMATION, - "enable_auto_pin_confirmation", false)) { + if (LockPatternUtils.isAutoPinConfirmFeatureAvailable()) { utils.setPinLength(userId, credentialCopy.size()); } credentialCopy.zeroize(); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 2f514795f342..4d820acd77b2 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -681,7 +681,7 @@ public class LockPatternUtils { * @return true, if deviceConfig flag is set to true or the flag is not propagated and * defaultValue is true. */ - public boolean isAutoPinConfirmFeatureAvailable() { + public static boolean isAutoPinConfirmFeatureAvailable() { return DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION, diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 92a9e566982c..82de7b8ec77e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2193,6 +2193,9 @@ <!-- The name of the package that will hold the system financed device controller role. --> <string name="config_systemFinancedDeviceController" translatable="false">com.android.devicelockcontroller</string> + <!-- The component name of the wear service class that will be started by the system server. --> + <string name="config_wearServiceComponent" translatable="false"></string> + <!-- The name of the package that will handle updating the device management role. --> <string name="config_devicePolicyManagementUpdater" translatable="false"></string> @@ -6207,6 +6210,18 @@ different from the home screen wallpaper. --> <bool name="config_independentLockscreenLiveWallpaper">false</bool> + <!-- Device state that corresponds to concurrent display mode where the default display + is the internal display. Public API for the feature is provided through Jetpack + WindowManager. + TODO(b/236022708) Move concurrent display state to device state config file + --> + <integer name="config_deviceStateConcurrentRearDisplay">-1</integer> + + <!-- Physical display address that corresponds to the rear display in rear display mode + and concurrent display mode. Used to get information about the display before + entering the corresponding modes --> + <string name="config_rearDisplayPhysicalAddress" translatable="false"></string> + <!-- List of certificate to be used for font fs-verity integrity verification --> <string-array translatable="false" name="config_fontManagerServiceCerts"> </string-array> @@ -6276,4 +6291,7 @@ "stopped" during initial boot of a device, or after an OTA update. Stopped state of an app is not changed during subsequent reboots. --> <bool name="config_stopSystemPackagesByDefault">false</bool> + + <!-- Whether to show weather on the lock screen by default. --> + <bool name="config_lockscreenWeatherEnabledByDefault">false</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f460219aaffc..aeb46cc18815 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4907,6 +4907,8 @@ <java-symbol type="string" name="concurrent_display_notification_thermal_content"/> <java-symbol type="string" name="device_state_notification_turn_off_button"/> <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/> + <java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" /> + <java-symbol type="string" name="config_rearDisplayPhysicalAddress" /> <!-- For app language picker --> <java-symbol type="string" name="system_locale_title" /> @@ -4926,4 +4928,8 @@ <java-symbol type="array" name="config_displayShapeArray" /> <java-symbol type="bool" name="config_stopSystemPackagesByDefault"/> + <java-symbol type="string" name="config_wearServiceComponent" /> + + <!-- Whether to show weather on the lockscreen by default. --> + <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" /> </resources> diff --git a/core/tests/coretests/src/android/app/admin/PackagePolicyTest.java b/core/tests/coretests/src/android/app/admin/PackagePolicyTest.java new file mode 100644 index 000000000000..d8298fdb284d --- /dev/null +++ b/core/tests/coretests/src/android/app/admin/PackagePolicyTest.java @@ -0,0 +1,181 @@ +/* + * 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 android.app.admin; + +import static android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST; +import static android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM; +import static android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST; + +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 android.os.Parcel; +import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class PackagePolicyTest { + + private static final String TEST_PACKAGE_NAME = "com.example"; + + private static final String TEST_PACKAGE_NAME_2 = "com.example.2"; + + private static final String TEST_SYSTEM_PACKAGE_NAME = "com.system"; + + + @Test + public void testParceling() { + final int policyType = PACKAGE_POLICY_BLOCKLIST; + final Set<String> packageNames = new ArraySet<>(); + packageNames.add(TEST_PACKAGE_NAME); + + final Parcel parcel = Parcel.obtain(); + PackagePolicy packagePolicy = new PackagePolicy(policyType, packageNames); + try { + packagePolicy.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + packagePolicy = PackagePolicy.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + + assertEquals(policyType, packagePolicy.getPolicyType()); + assertNotNull(packagePolicy.getPackageNames()); + assertEquals(1, packagePolicy.getPackageNames().size()); + assertTrue(packagePolicy.getPackageNames().contains(TEST_PACKAGE_NAME)); + } + + @Test + public void testEmptyBlocklistCreation() { + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_BLOCKLIST); + assertEquals(PACKAGE_POLICY_BLOCKLIST, policy.getPolicyType()); + assertNotNull(policy.getPackageNames()); + assertTrue(policy.getPackageNames().isEmpty()); + } + + @Test + public void testEmptyAllowlistCreation() { + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST); + assertEquals(PACKAGE_POLICY_ALLOWLIST, policy.getPolicyType()); + assertNotNull(policy.getPackageNames()); + assertTrue(policy.getPackageNames().isEmpty()); + } + + @Test + public void testEmptyAllowlistAndSystemCreation() { + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM); + assertEquals(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, policy.getPolicyType()); + assertNotNull(policy.getPackageNames()); + assertTrue(policy.getPackageNames().isEmpty()); + } + + @Test + public void testSuppliedBlocklistCreation() { + final Set<String> packageNames = new ArraySet<>(); + packageNames.add(TEST_PACKAGE_NAME); + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_BLOCKLIST, packageNames); + assertEquals(PACKAGE_POLICY_BLOCKLIST, policy.getPolicyType()); + assertNotNull(policy.getPackageNames()); + assertEquals(1, policy.getPackageNames().size()); + assertTrue(policy.getPackageNames().contains(TEST_PACKAGE_NAME)); + } + + @Test + public void testSuppliedAllowlistCreation() { + final Set<String> packageNames = new ArraySet<>(); + packageNames.add(TEST_PACKAGE_NAME); + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST, packageNames); + assertEquals(PACKAGE_POLICY_ALLOWLIST, policy.getPolicyType()); + assertNotNull(policy.getPackageNames()); + assertEquals(1, policy.getPackageNames().size()); + assertTrue(policy.getPackageNames().contains(TEST_PACKAGE_NAME)); + } + + @Test + public void testSuppliedAllowlistAndSystemCreation() { + final Set<String> packageNames = new ArraySet<>(); + packageNames.add(TEST_PACKAGE_NAME); + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, packageNames); + assertEquals(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, policy.getPolicyType()); + assertNotNull(policy.getPackageNames()); + assertEquals(1, policy.getPackageNames().size()); + assertTrue(policy.getPackageNames().contains(TEST_PACKAGE_NAME)); + } + + @Test + public void testBlocklist_isPackageAllowed() { + final Set<String> packageNames = new ArraySet<>(); + packageNames.add(TEST_PACKAGE_NAME); + final Set<String> systemPackages = new ArraySet<>(); + systemPackages.add(TEST_SYSTEM_PACKAGE_NAME); + + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_BLOCKLIST, packageNames); + + assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME, Collections.emptySet())); + assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME, systemPackages)); + assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, Collections.emptySet())); + assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, systemPackages)); + assertTrue(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, Collections.emptySet())); + assertTrue(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, systemPackages)); + } + + @Test + public void testAllowlist_isPackageAllowed() { + final Set<String> packageNames = new ArraySet<>(); + packageNames.add(TEST_PACKAGE_NAME); + final Set<String> systemPackages = new ArraySet<>(); + systemPackages.add(TEST_SYSTEM_PACKAGE_NAME); + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST, packageNames); + + assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, Collections.emptySet())); + assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, systemPackages)); + assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, Collections.emptySet())); + assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, systemPackages)); + assertFalse(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, Collections.emptySet())); + assertFalse(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, systemPackages)); + } + + @Test + public void testAllowlistAndSystem_isPackageAllowed() { + final Set<String> packageNames = new ArraySet<>(); + packageNames.add(TEST_PACKAGE_NAME); + final Set<String> systemPackages = new ArraySet<>(); + systemPackages.add(TEST_SYSTEM_PACKAGE_NAME); + PackagePolicy policy = new PackagePolicy(PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, packageNames); + + assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, Collections.emptySet())); + assertTrue(policy.isPackageAllowed(TEST_PACKAGE_NAME, systemPackages)); + assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, Collections.emptySet())); + assertFalse(policy.isPackageAllowed(TEST_PACKAGE_NAME_2, systemPackages)); + assertFalse(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, Collections.emptySet())); + assertTrue(policy.isPackageAllowed(TEST_SYSTEM_PACKAGE_NAME, systemPackages)); + } + +} diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java index 048c48bc45fa..f79ba28d946f 100644 --- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java +++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java @@ -67,7 +67,7 @@ public class RegisterStatusBarResultTest { BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE, WindowInsets.Type.defaultVisible(), "test" /* packageName */, - new int[0] /* transientBarTypes */, + 0 /* transientBarTypes */, new LetterboxDetails[] {letterboxDetails}); final RegisterStatusBarResult copy = clone(original); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index f2d625046732..53f47475d2f3 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -61,6 +61,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/WindowToken.java" }, + "-2088209279": { + "message": "Notified TransitionController that the display is ready.", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "-2072089308": { "message": "Attempted to add window with token that is a sub-window: %s. Aborting.", "level": "WARN", @@ -2767,6 +2773,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "378890013": { + "message": "Apply and finish immediately because player is disabled for transition #%d .", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "385237117": { "message": "moveFocusableActivityToTop: already on top and focused, activity=%s", "level": "DEBUG", @@ -3649,6 +3661,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1282992082": { + "message": "Disabling player for transition #%d because display isn't enabled yet", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "1284122013": { "message": "TaskFragment appeared name=%s", "level": "VERBOSE", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java new file mode 100644 index 000000000000..1ff169433b9d --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java @@ -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 androidx.window.extensions.area; + +import android.app.Presentation; +import android.content.Context; +import android.view.Display; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.window.extensions.core.util.function.Consumer; + +/** + * {@link Presentation} object that is used to present extra content + * on the rear facing display when in a rear display presentation feature. + */ +class RearDisplayPresentation extends Presentation implements ExtensionWindowAreaPresentation { + + @NonNull + private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer; + + RearDisplayPresentation(@NonNull Context outerContext, @NonNull Display display, + @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) { + super(outerContext, display); + mStateConsumer = stateConsumer; + } + + /** + * {@code mStateConsumer} is notified that their content is now visible when the + * {@link Presentation} object is started. There is no comparable callback for + * {@link WindowAreaComponent#SESSION_STATE_INVISIBLE} in {@link #onStop()} due to the + * timing of when a {@link android.hardware.devicestate.DeviceStateRequest} is cancelled + * ending rear display presentation mode happening before the {@link Presentation} is stopped. + */ + @Override + protected void onStart() { + super.onStart(); + mStateConsumer.accept(WindowAreaComponent.SESSION_STATE_VISIBLE); + } + + @NonNull + @Override + public Context getPresentationContext() { + return getContext(); + } + + @Override + public void setPresentationView(View view) { + setContentView(view); + if (!isShowing()) { + show(); + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java new file mode 100644 index 000000000000..141a6ad48771 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java @@ -0,0 +1,100 @@ +/* + * 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 androidx.window.extensions.area; + +import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_ACTIVE; +import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_INACTIVE; + +import android.content.Context; +import android.hardware.devicestate.DeviceStateRequest; +import android.hardware.display.DisplayManager; +import android.util.Log; +import android.view.Display; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.window.extensions.core.util.function.Consumer; + +import java.util.Objects; + +/** + * Controller class that keeps track of the status of the device state request + * to enable the rear display presentation feature. This controller notifies the session callback + * when the state request is active, and notifies the callback when the request is canceled. + * + * Clients are notified via {@link Consumer} provided with + * {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} values to signify + * when the request becomes active and cancelled. + */ +class RearDisplayPresentationController implements DeviceStateRequest.Callback { + + private static final String TAG = "RearDisplayPresentationController"; + + // Original context that requested to enable rear display presentation mode + @NonNull + private final Context mContext; + @NonNull + private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer; + @Nullable + private ExtensionWindowAreaPresentation mExtensionWindowAreaPresentation; + @NonNull + private final DisplayManager mDisplayManager; + + /** + * Creates the RearDisplayPresentationController + * @param context Originating {@link android.content.Context} that is initiating the rear + * display presentation session. + * @param stateConsumer {@link Consumer} that will be notified that the session is active when + * the device state request is active and the session has been created. If the device + * state request is cancelled, the callback will be notified that the session has been + * ended. This could occur through a call to cancel the feature or if the device is + * manipulated in a way that cancels any device state override. + */ + RearDisplayPresentationController(@NonNull Context context, + @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) { + Objects.requireNonNull(context); + Objects.requireNonNull(stateConsumer); + + mContext = context; + mStateConsumer = stateConsumer; + mDisplayManager = context.getSystemService(DisplayManager.class); + } + + @Override + public void onRequestActivated(@NonNull DeviceStateRequest request) { + Display[] rearDisplays = mDisplayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_REAR); + if (rearDisplays.length == 0) { + mStateConsumer.accept(SESSION_STATE_INACTIVE); + Log.e(TAG, "Rear display list should not be empty"); + return; + } + + mExtensionWindowAreaPresentation = + new RearDisplayPresentation(mContext, rearDisplays[0], mStateConsumer); + mStateConsumer.accept(SESSION_STATE_ACTIVE); + } + + @Override + public void onRequestCanceled(@NonNull DeviceStateRequest request) { + mStateConsumer.accept(SESSION_STATE_INACTIVE); + } + + @Nullable + public ExtensionWindowAreaPresentation getWindowAreaPresentation() { + return mExtensionWindowAreaPresentation; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java new file mode 100644 index 000000000000..0b1423ae48c0 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java @@ -0,0 +1,62 @@ +/* + * 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 androidx.window.extensions.area; + +import android.util.DisplayMetrics; + +import androidx.annotation.NonNull; + +/** + * Class that provides information around the current status of a window area feature. Contains + * the current {@link WindowAreaComponent.WindowAreaStatus} value corresponding to the + * rear display presentation feature, as well as the {@link DisplayMetrics} for the rear facing + * display. + */ +class RearDisplayPresentationStatus implements ExtensionWindowAreaStatus { + + @WindowAreaComponent.WindowAreaStatus + private final int mWindowAreaStatus; + + @NonNull + private final DisplayMetrics mDisplayMetrics; + + RearDisplayPresentationStatus(@WindowAreaComponent.WindowAreaStatus int status, + @NonNull DisplayMetrics displayMetrics) { + mWindowAreaStatus = status; + mDisplayMetrics = displayMetrics; + } + + /** + * Returns the {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} + * value that relates to the current status of a feature. + */ + @Override + @WindowAreaComponent.WindowAreaStatus + public int getWindowAreaStatus() { + return mWindowAreaStatus; + } + + /** + * Returns the {@link DisplayMetrics} that corresponds to the window area that a feature + * interacts with. This is converted to size class information provided to developers. + */ + @Override + @NonNull + public DisplayMetrics getWindowAreaDisplayMetrics() { + return mDisplayMetrics; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 20602a1b290e..274dcaee5a43 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -22,7 +22,12 @@ import android.app.Activity; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateRequest; +import android.hardware.display.DisplayManager; import android.util.ArraySet; +import android.util.DisplayMetrics; +import android.util.Pair; +import android.view.Display; +import android.view.DisplayAddress; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -30,6 +35,7 @@ import androidx.window.extensions.core.util.function.Consumer; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import java.util.concurrent.Executor; @@ -47,61 +53,102 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, private final Object mLock = new Object(); + @NonNull private final DeviceStateManager mDeviceStateManager; + @NonNull + private final DisplayManager mDisplayManager; + @NonNull private final Executor mExecutor; @GuardedBy("mLock") private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>(); + @GuardedBy("mLock") + private final ArraySet<Consumer<ExtensionWindowAreaStatus>> + mRearDisplayPresentationStatusListeners = new ArraySet<>(); private final int mRearDisplayState; + private final int mConcurrentDisplayState; + @NonNull + private final int[] mFoldedDeviceStates; + @NonNull + private long mRearDisplayAddress = 0; @WindowAreaSessionState private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; @GuardedBy("mLock") private int mCurrentDeviceState = INVALID_DEVICE_STATE; @GuardedBy("mLock") - private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE; + private int[] mCurrentSupportedDeviceStates; + + @GuardedBy("mLock") + private DeviceStateRequest mRearDisplayStateRequest; + @GuardedBy("mLock") + private RearDisplayPresentationController mRearDisplayPresentationController; + + @Nullable + @GuardedBy("mLock") + private DisplayMetrics mRearDisplayMetrics; + + @WindowAreaSessionState @GuardedBy("mLock") - private DeviceStateRequest mDeviceStateRequest; + private int mLastReportedRearDisplayPresentationStatus; public WindowAreaComponentImpl(@NonNull Context context) { mDeviceStateManager = context.getSystemService(DeviceStateManager.class); + mDisplayManager = context.getSystemService(DisplayManager.class); mExecutor = context.getMainExecutor(); + mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates(); + mFoldedDeviceStates = context.getResources().getIntArray( + R.array.config_foldedDeviceStates); + // TODO(b/236022708) Move rear display state to device state config file mRearDisplayState = context.getResources().getInteger( R.integer.config_deviceStateRearDisplay); + mConcurrentDisplayState = context.getResources().getInteger( + R.integer.config_deviceStateConcurrentRearDisplay); + mDeviceStateManager.registerCallback(mExecutor, this); + if (mConcurrentDisplayState != INVALID_DEVICE_STATE) { + mRearDisplayAddress = Long.parseLong(context.getResources().getString( + R.string.config_rearDisplayPhysicalAddress)); + } } /** * Adds a listener interested in receiving updates on the RearDisplayStatus * of the device. Because this is being called from the OEM provided - * extensions, we will post the result of the listener on the executor + * extensions, the result of the listener will be posted on the executor * provided by the developer at the initial call site. * - * Depending on the initial state of the device, we will return either + * Rear display mode moves the calling application to the display on the device that is + * facing the same direction as the rear cameras. This would be the cover display on a fold-in + * style device when the device is opened. + * + * Depending on the initial state of the device, the {@link Consumer} will receive either * {@link WindowAreaComponent#STATUS_AVAILABLE} or * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that - * state respectively. When the rear display feature is triggered, we update the status to be - * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_ + * state respectively. When the rear display feature is triggered, the status is updated to be + * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. + * TODO(b/240727590): Prefix with AREA_ * - * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently - * enabled. + * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently + * enabled. * * @param consumer {@link Consumer} interested in receiving updates to the status of * rear display mode. */ + @Override public void addRearDisplayStatusListener( @NonNull Consumer<@WindowAreaStatus Integer> consumer) { synchronized (mLock) { mRearDisplayStatusListeners.add(consumer); - // If current device state is still invalid, we haven't gotten our initial value yet + // If current device state is still invalid, the initial value has not been provided. if (mCurrentDeviceState == INVALID_DEVICE_STATE) { return; } - consumer.accept(getCurrentStatus()); + consumer.accept(getCurrentRearDisplayModeStatus()); } } @@ -109,6 +156,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, * Removes a listener no longer interested in receiving updates. * @param consumer no longer interested in receiving updates to RearDisplayStatus */ + @Override public void removeRearDisplayStatusListener( @NonNull Consumer<@WindowAreaStatus Integer> consumer) { synchronized (mLock) { @@ -119,13 +167,17 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, /** * Creates and starts a rear display session and provides updates to the * callback provided. Because this is being called from the OEM provided - * extensions, we will post the result of the listener on the executor + * extensions, the result of the listener will be posted on the executor * provided by the developer at the initial call site. * - * When we enable rear display mode, we submit a request to {@link DeviceStateManager} + * Rear display mode moves the calling application to the display on the device that is + * facing the same direction as the rear cameras. This would be the cover display on a fold-in + * style device when the device is opened. + * + * When rear display mode is enabled, a request is made to {@link DeviceStateManager} * to override the device state to the state that corresponds to RearDisplay - * mode. When the {@link DeviceStateRequest} is activated, we let the - * consumer know that the session is active by sending + * mode. When the {@link DeviceStateRequest} is activated, the provided {@link Consumer} is + * notified that the session is active by receiving * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}. * * @param activity to provide updates to the client on @@ -133,19 +185,20 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, * @param rearDisplaySessionCallback to provide updates to the client on * the status of the Session */ + @Override public void startRearDisplaySession(@NonNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) { synchronized (mLock) { - if (mDeviceStateRequest != null) { + if (mRearDisplayStateRequest != null) { // Rear display session is already active throw new IllegalStateException( "Unable to start new rear display session as one is already active"); } - mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build(); + mRearDisplayStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build(); mDeviceStateManager.requestState( - mDeviceStateRequest, + mRearDisplayStateRequest, mExecutor, - new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback) + new RearDisplayStateRequestCallbackAdapter(rearDisplaySessionCallback) ); } } @@ -153,13 +206,14 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, /** * Ends the current rear display session and provides updates to the * callback provided. Because this is being called from the OEM provided - * extensions, we will post the result of the listener on the executor - * provided by the developer. + * extensions, the result of the listener will be posted on the executor + * provided by the developer at the initial call site. */ + @Override public void endRearDisplaySession() { synchronized (mLock) { - if (mDeviceStateRequest != null || isRearDisplayActive()) { - mDeviceStateRequest = null; + if (mRearDisplayStateRequest != null || isRearDisplayActive()) { + mRearDisplayStateRequest = null; mDeviceStateManager.cancelStateRequest(); } else { throw new IllegalStateException( @@ -168,48 +222,194 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } } + /** + * Adds a listener interested in receiving updates on the RearDisplayPresentationStatus + * of the device. Because this is being called from the OEM provided + * extensions, the result of the listener will be posted on the executor + * provided by the developer at the initial call site. + * + * Rear display presentation mode is a feature where an {@link Activity} can present + * additional content on a device with a second display that is facing the same direction + * as the rear camera (i.e. the cover display on a fold-in style device). The calling + * {@link Activity} does not move, whereas in rear display mode it does. + * + * This listener receives a {@link Pair} with the first item being the + * {@link WindowAreaComponent.WindowAreaStatus} that corresponds to the current status of the + * feature, and the second being the {@link DisplayMetrics} of the display that would be + * presented to when the feature is active. + * + * Depending on the initial state of the device, the {@link Consumer} will receive either + * {@link WindowAreaComponent#STATUS_AVAILABLE} or + * {@link WindowAreaComponent#STATUS_UNAVAILABLE} for the status value of the {@link Pair} if + * the feature is supported or not in that state respectively. Rear display presentation mode is + * currently not supported when the device is folded. When the rear display presentation feature + * is triggered, the status is updated to be {@link WindowAreaComponent#STATUS_UNAVAILABLE}. + * TODO(b/240727590): Prefix with AREA_ + * + * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently + * enabled. + * + * @param consumer {@link Consumer} interested in receiving updates to the status of + * rear display presentation mode. + */ @Override - public void onBaseStateChanged(int state) { + public void addRearDisplayPresentationStatusListener( + @NonNull Consumer<ExtensionWindowAreaStatus> consumer) { synchronized (mLock) { - mCurrentDeviceBaseState = state; - if (state == mCurrentDeviceState) { - updateStatusConsumers(getCurrentStatus()); + mRearDisplayPresentationStatusListeners.add(consumer); + + // If current device state is still invalid, the initial value has not been provided + if (mCurrentDeviceState == INVALID_DEVICE_STATE) { + return; } + @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus(); + consumer.accept( + new RearDisplayPresentationStatus(currentStatus, getRearDisplayMetrics())); } } + /** + * Removes a listener no longer interested in receiving updates. + * @param consumer no longer interested in receiving updates to RearDisplayPresentationStatus + */ @Override - public void onStateChanged(int state) { + public void removeRearDisplayPresentationStatusListener( + @NonNull Consumer<ExtensionWindowAreaStatus> consumer) { synchronized (mLock) { - mCurrentDeviceState = state; - updateStatusConsumers(getCurrentStatus()); + mRearDisplayPresentationStatusListeners.remove(consumer); } } + /** + * Creates and starts a rear display presentation session and sends state updates to the + * consumer provided. This consumer will receive a constant represented by + * {@link WindowAreaSessionState} to represent the state of the current rear display + * session. It will be translated to a more friendly interface in the library. + * + * Because this is being called from the OEM provided extensions, the library + * will post the result of the listener on the executor provided by the developer. + * + * Rear display presentation mode refers to a feature where an {@link Activity} can present + * additional content on a device with a second display that is facing the same direction + * as the rear camera (i.e. the cover display on a fold-in style device). The calling + * {@link Activity} stays on the user-facing display. + * + * @param activity that the OEM implementation will use as a base + * context and to identify the source display area of the request. + * The reference to the activity instance must not be stored in the OEM + * implementation to prevent memory leaks. + * @param consumer to provide updates to the client on the status of the session + * @throws UnsupportedOperationException if this method is called when rear display presentation + * mode is not available. This could be to an incompatible device state or when + * another process is currently in this mode. + */ @Override - public void addRearDisplayPresentationStatusListener( - @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {} + public void startRearDisplayPresentationSession(@NonNull Activity activity, + @NonNull Consumer<@WindowAreaSessionState Integer> consumer) { + synchronized (mLock) { + if (mRearDisplayPresentationController != null) { + // Rear display presentation session is already active + throw new IllegalStateException( + "Unable to start new rear display presentation session as one is already " + + "active"); + } + if (getCurrentRearDisplayPresentationModeStatus() + != WindowAreaComponent.STATUS_AVAILABLE) { + throw new IllegalStateException( + "Unable to start new rear display presentation session as the feature is " + + "is not currently available"); + } + mRearDisplayPresentationController = new RearDisplayPresentationController(activity, + stateStatus -> { + synchronized (mLock) { + if (stateStatus == SESSION_STATE_INACTIVE) { + // If the last reported session status was VISIBLE + // then the INVISIBLE state should be dispatched before INACTIVE + // due to not having a good mechanism to know when + // the content is no longer visible before it's fully removed + if (getLastReportedRearDisplayPresentationStatus() + == SESSION_STATE_VISIBLE) { + consumer.accept(SESSION_STATE_INVISIBLE); + } + mRearDisplayPresentationController = null; + } + mLastReportedRearDisplayPresentationStatus = stateStatus; + consumer.accept(stateStatus); + } + }); + + DeviceStateRequest concurrentDisplayStateRequest = DeviceStateRequest.newBuilder( + mConcurrentDisplayState).build(); + mDeviceStateManager.requestState( + concurrentDisplayStateRequest, + mExecutor, + mRearDisplayPresentationController + ); + } + } + + /** + * Ends the current rear display presentation session and provides updates to the + * callback provided. When this is ended, the presented content from the calling + * {@link Activity} will also be removed from the rear facing display. + * Because this is being called from the OEM provided extensions, the result of the listener + * will be posted on the executor provided by the developer at the initial call site. + * + * Cancelling the {@link DeviceStateRequest} and exiting the rear display presentation state, + * will remove the presentation window from the cover display as the cover display is no longer + * enabled. + */ @Override - public void removeRearDisplayPresentationStatusListener( - @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {} + public void endRearDisplayPresentationSession() { + synchronized (mLock) { + if (mRearDisplayPresentationController != null) { + mDeviceStateManager.cancelStateRequest(); + } else { + throw new IllegalStateException( + "Unable to cancel a rear display presentation session as there is no " + + "active session"); + } + } + } + @Nullable @Override - public void startRearDisplayPresentationSession(@NonNull Activity activity, - @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {} + public ExtensionWindowAreaPresentation getRearDisplayPresentation() { + synchronized (mLock) { + ExtensionWindowAreaPresentation presentation = null; + if (mRearDisplayPresentationController != null) { + presentation = mRearDisplayPresentationController.getWindowAreaPresentation(); + } + return presentation; + } + } @Override - public void endRearDisplayPresentationSession() {} + public void onSupportedStatesChanged(int[] supportedStates) { + synchronized (mLock) { + mCurrentSupportedDeviceStates = supportedStates; + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); + updateRearDisplayPresentationStatusListeners( + getCurrentRearDisplayPresentationModeStatus()); + } + } @Override - @Nullable - public ExtensionWindowAreaPresentation getRearDisplayPresentation() { - return null; + public void onStateChanged(int state) { + synchronized (mLock) { + mCurrentDeviceState = state; + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); + updateRearDisplayPresentationStatusListeners( + getCurrentRearDisplayPresentationModeStatus()); + } } + @GuardedBy("mLock") - private int getCurrentStatus() { + private int getCurrentRearDisplayModeStatus() { if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE + || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState) || isRearDisplayActive()) { return WindowAreaComponent.STATUS_UNAVAILABLE; } @@ -218,19 +418,20 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, /** * Helper method to determine if a rear display session is currently active by checking - * if the current device configuration matches that of rear display. This would be true - * if there is a device override currently active (base state != current state) and the current - * state is that which corresponds to {@code mRearDisplayState} - * @return {@code true} if the device is in rear display mode and {@code false} if not + * if the current device state is that which corresponds to {@code mRearDisplayState}. + * + * @return {@code true} if the device is in rear display state {@code false} if not */ @GuardedBy("mLock") private boolean isRearDisplayActive() { - return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState - == mRearDisplayState); + return mCurrentDeviceState == mRearDisplayState; } @GuardedBy("mLock") - private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) { + private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) { + if (mRearDisplayState == INVALID_DEVICE_STATE) { + return; + } synchronized (mLock) { for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) { mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus); @@ -238,26 +439,95 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } } + @GuardedBy("mLock") + private int getCurrentRearDisplayPresentationModeStatus() { + if (mCurrentDeviceState == mConcurrentDisplayState + || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState) + || isDeviceFolded()) { + return WindowAreaComponent.STATUS_UNAVAILABLE; + } + return WindowAreaComponent.STATUS_AVAILABLE; + } + + @GuardedBy("mLock") + private boolean isDeviceFolded() { + return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState); + } + + @GuardedBy("mLock") + private void updateRearDisplayPresentationStatusListeners( + @WindowAreaStatus int windowAreaStatus) { + if (mConcurrentDisplayState == INVALID_DEVICE_STATE) { + return; + } + RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus( + windowAreaStatus, getRearDisplayMetrics()); + synchronized (mLock) { + for (int i = 0; i < mRearDisplayPresentationStatusListeners.size(); i++) { + mRearDisplayPresentationStatusListeners.valueAt(i).accept(consumerValue); + } + } + } + + /** + * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing + * display was not found in the display list, but we have already computed the + * {@link DisplayMetrics} for that display, we return the cached value. + * + * TODO(b/267563768): Update with guidance from Display team for missing displays. + * + * @throws IllegalArgumentException if the display is not found and there is no cached + * {@link DisplayMetrics} for this display. + */ + @GuardedBy("mLock") + private DisplayMetrics getRearDisplayMetrics() { + Display[] displays = mDisplayManager.getDisplays( + DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); + for (int i = 0; i < displays.length; i++) { + DisplayAddress.Physical address = + (DisplayAddress.Physical) displays[i].getAddress(); + if (mRearDisplayAddress == address.getPhysicalDisplayId()) { + if (mRearDisplayMetrics == null) { + mRearDisplayMetrics = new DisplayMetrics(); + } + displays[i].getRealMetrics(mRearDisplayMetrics); + return mRearDisplayMetrics; + } + } + if (mRearDisplayMetrics != null) { + return mRearDisplayMetrics; + } else { + throw new IllegalArgumentException( + "No display found with the provided display address"); + } + } + + @GuardedBy("mLock") + @WindowAreaSessionState + private int getLastReportedRearDisplayPresentationStatus() { + return mLastReportedRearDisplayPresentationStatus; + } + /** * Callback for the {@link DeviceStateRequest} to be notified of when the request has been * activated or cancelled. This callback provides information to the client library * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback} */ - private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback { + private class RearDisplayStateRequestCallbackAdapter implements DeviceStateRequest.Callback { private final Consumer<Integer> mRearDisplaySessionCallback; - DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) { + RearDisplayStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) { mRearDisplaySessionCallback = callback; } @Override public void onRequestActivated(@NonNull DeviceStateRequest request) { synchronized (mLock) { - if (request.equals(mDeviceStateRequest)) { + if (request.equals(mRearDisplayStateRequest)) { mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE; mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); - updateStatusConsumers(getCurrentStatus()); + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); } } } @@ -265,12 +535,12 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, @Override public void onRequestCanceled(DeviceStateRequest request) { synchronized (mLock) { - if (request.equals(mDeviceStateRequest)) { - mDeviceStateRequest = null; + if (request.equals(mRearDisplayStateRequest)) { + mRearDisplayStateRequest = null; } mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); - updateStatusConsumers(getCurrentStatus()); + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); } } } diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 378ad811bd22..7cd5dd69ef8f 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index 5d451a5006e1..7a6aec718006 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -173,7 +173,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, */ public void onLocationChanged() { getBoundsOnScreen(mTmpRect); - mTaskViewTaskController.onLocationChanged(mTmpRect); + mTaskViewTaskController.setWindowBounds(mTmpRect); } /** @@ -198,7 +198,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, public void surfaceChanged(@androidx.annotation.NonNull SurfaceHolder holder, int format, int width, int height) { getBoundsOnScreen(mTmpRect); - mTaskViewTaskController.surfaceChanged(mTmpRect); + mTaskViewTaskController.setWindowBounds(mTmpRect); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java index 69ce35f43401..1f223a6ec5d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTaskController.java @@ -183,26 +183,6 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } /** - * Call when view position or size has changed. Do not call when animating. - */ - public void onLocationChanged(Rect newBounds) { - if (mTaskToken == null) { - return; - } - // Sync Transactions can't operate simultaneously with shell transition collection. - // The transition animation (upon showing) will sync the location itself. - if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return; - - WindowContainerTransaction wct = new WindowContainerTransaction(); - updateWindowBounds(wct); - mSyncQueue.queue(wct); - } - - private void updateWindowBounds(WindowContainerTransaction wct) { - wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen()); - } - - /** * Release this container if it is initialized. */ public void release() { @@ -394,15 +374,24 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } /** - * Should be called when the client surface is changed. + * Sets the window bounds to {@code boundsOnScreen}. + * Call when view position or size has changed. Can also be called before the animation when + * the final bounds are known. + * Do not call during the animation. * * @param boundsOnScreen the on screen bounds of the surface view. */ - public void surfaceChanged(Rect boundsOnScreen) { + public void setWindowBounds(Rect boundsOnScreen) { if (mTaskToken == null) { return; } - onLocationChanged(boundsOnScreen); + // Sync Transactions can't operate simultaneously with shell transition collection. + // The transition animation (upon showing) will sync the location itself. + if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return; + + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mTaskToken, boundsOnScreen); + mSyncQueue.queue(wct); } /** Should be called when the client surface is destroyed. */ @@ -493,7 +482,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { .setPosition(mTaskLeash, 0, 0) .apply(); - updateWindowBounds(wct); + wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen()); } else { // The surface has already been destroyed before the task has appeared, // so go ahead and hide the task entirely diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index e36dfc3e49be..36c0cb6dfe19 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1058,6 +1058,11 @@ public class BubbleController implements ConfigurationChangeListener { } } + /** Sets the app bubble's taskId which is cached for SysUI. */ + public void setAppBubbleTaskId(int taskId) { + mImpl.mCachedState.setAppBubbleTaskId(taskId); + } + /** * Fills the overflow bubbles by loading them from disk. */ @@ -1636,6 +1641,7 @@ public class BubbleController implements ConfigurationChangeListener { private HashSet<String> mSuppressedBubbleKeys = new HashSet<>(); private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>(); private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>(); + private int mAppBubbleTaskId = INVALID_TASK_ID; private ArrayList<Bubble> mTmpBubbles = new ArrayList<>(); @@ -1667,12 +1673,22 @@ public class BubbleController implements ConfigurationChangeListener { mSuppressedBubbleKeys.clear(); mShortcutIdToBubble.clear(); + mAppBubbleTaskId = INVALID_TASK_ID; for (Bubble b : mTmpBubbles) { mShortcutIdToBubble.put(b.getShortcutId(), b); updateBubbleSuppressedState(b); + + if (KEY_APP_BUBBLE.equals(b.getKey())) { + mAppBubbleTaskId = b.getTaskId(); + } } } + /** Sets the app bubble's taskId which is cached for SysUI. */ + synchronized void setAppBubbleTaskId(int taskId) { + mAppBubbleTaskId = taskId; + } + /** * Updates a specific bubble suppressed state. This is used mainly because notification * suppression changes don't go through the same BubbleData update mechanism. @@ -1722,6 +1738,8 @@ public class BubbleController implements ConfigurationChangeListener { for (String key : mSuppressedGroupToNotifKeys.keySet()) { pw.println(" suppressing: " + key); } + + pw.print("mAppBubbleTaskId: " + mAppBubbleTaskId); } } @@ -1773,8 +1791,7 @@ public class BubbleController implements ConfigurationChangeListener { @Override public boolean isAppBubbleTaskId(int taskId) { - Bubble appBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE); - return appBubble != null && appBubble.getTaskId() == taskId; + return mCachedState.mAppBubbleTaskId == taskId; } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 1feff18fd68e..57c7731e69ed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -278,6 +278,11 @@ public class BubbleExpandedView extends LinearLayout { // The taskId is saved to use for removeTask, preventing appearance in recent tasks. mTaskId = taskId; + if (Bubble.KEY_APP_BUBBLE.equals(getBubbleKey())) { + // Let the controller know sooner what the taskId is. + mController.setAppBubbleTaskId(mTaskId); + } + // With the task org, the taskAppeared callback will only happen once the task has // already drawn setContentVisibility(true); 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 45b234a6398a..f616e6f64750 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 @@ -699,19 +699,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return bounds.width() > bounds.height(); } - /** Reverse the split position. */ - @SplitPosition - public static int reversePosition(@SplitPosition int position) { - switch (position) { - case SPLIT_POSITION_TOP_OR_LEFT: - return SPLIT_POSITION_BOTTOM_OR_RIGHT; - case SPLIT_POSITION_BOTTOM_OR_RIGHT: - return SPLIT_POSITION_TOP_OR_LEFT; - default: - return SPLIT_POSITION_UNDEFINED; - } - } - /** * Return if this layout is landscape. */ 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 new file mode 100644 index 000000000000..042721c97053 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -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.wm.shell.common.split; + +import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; +import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; + +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.content.Intent; + +import com.android.internal.util.ArrayUtils; +import com.android.wm.shell.ShellTaskOrganizer; + +/** Helper utility class for split screen components to use. */ +public class SplitScreenUtils { + /** Reverse the split position. */ + @SplitScreenConstants.SplitPosition + public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) { + switch (position) { + case SPLIT_POSITION_TOP_OR_LEFT: + return SPLIT_POSITION_BOTTOM_OR_RIGHT; + case SPLIT_POSITION_BOTTOM_OR_RIGHT: + return SPLIT_POSITION_TOP_OR_LEFT; + case SPLIT_POSITION_UNDEFINED: + default: + return SPLIT_POSITION_UNDEFINED; + } + } + + /** Returns true if the task is valid for split screen. */ + public static boolean isValidToSplit(ActivityManager.RunningTaskInfo taskInfo) { + return taskInfo != null && taskInfo.supportsMultiWindow + && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()) + && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()); + } + + /** Retrieve package name from an intent */ + @Nullable + public static String getPackageName(Intent intent) { + if (intent == null || intent.getComponent() == null) { + return null; + } + return intent.getComponent().getPackageName(); + } + + /** Retrieve package name from a PendingIntent */ + @Nullable + public static String getPackageName(PendingIntent pendingIntent) { + if (pendingIntent == null || pendingIntent.getIntent() == null) { + return null; + } + return getPackageName(pendingIntent.getIntent()); + } + + /** Retrieve package name from a taskId */ + @Nullable + public static String getPackageName(int taskId, ShellTaskOrganizer taskOrganizer) { + final ActivityManager.RunningTaskInfo taskInfo = taskOrganizer.getRunningTaskInfo(taskId); + return taskInfo != null ? getPackageName(taskInfo.baseIntent) : null; + } + + /** Returns true if they are the same package. */ + public static boolean samePackage(String packageName1, String packageName2) { + return packageName1 != null && packageName1.equals(packageName2); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 6728c00af51b..d9ac76e833d8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -28,6 +28,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.app.TaskInfo; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.view.Surface; import android.view.SurfaceControl; @@ -361,22 +362,26 @@ public class PipAnimationController { } void setColorContentOverlay(Context context) { - final SurfaceControl.Transaction tx = - mSurfaceControlTransactionFactory.getTransaction(); - if (mContentOverlay != null) { - mContentOverlay.detach(tx); - } - mContentOverlay = new PipContentOverlay.PipColorOverlay(context); - mContentOverlay.attach(tx, mLeash); + reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context)); } void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { + reattachContentOverlay( + new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint)); + } + + void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) { + reattachContentOverlay( + new PipContentOverlay.PipAppIconOverlay(context, bounds, activityInfo)); + } + + private void reattachContentOverlay(PipContentOverlay overlay) { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); if (mContentOverlay != null) { mContentOverlay.detach(tx); } - mContentOverlay = new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint); + mContentOverlay = overlay; mContentOverlay.attach(tx, mLeash); } @@ -570,8 +575,9 @@ public class PipAnimationController { final Rect base = getBaseValue(); final Rect start = getStartValue(); final Rect end = getEndValue(); + Rect bounds = mRectEvaluator.evaluate(fraction, start, end); if (mContentOverlay != null) { - mContentOverlay.onAnimationUpdate(tx, fraction); + mContentOverlay.onAnimationUpdate(tx, bounds, fraction); } if (rotatedEndRect != null) { // Animate the bounds in a different orientation. It only happens when @@ -579,7 +585,6 @@ public class PipAnimationController { applyRotation(tx, leash, fraction, start, end); return; } - Rect bounds = mRectEvaluator.evaluate(fraction, start, end); float angle = (1.0f - fraction) * startingAngle; setCurrentValue(bounds); if (inScaleTransition() || sourceHintRect == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index 283b1ec0f752..480bf93b2ddb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -16,11 +16,21 @@ package com.android.wm.shell.pip; +import static android.util.TypedValue.COMPLEX_UNIT_DIP; + import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.TypedValue; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.TaskSnapshot; @@ -51,9 +61,11 @@ public abstract class PipContentOverlay { * Animates the internal {@link #mLeash} by a given fraction. * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly * call apply on this transaction, it should be applied on the caller side. + * @param currentBounds {@link Rect} of the current animation bounds. * @param fraction progress of the animation ranged from 0f to 1f. */ - public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction); + public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction); /** * Callback when reaches the end of animation on the internal {@link #mLeash}. @@ -66,13 +78,15 @@ public abstract class PipContentOverlay { /** A {@link PipContentOverlay} uses solid color. */ public static final class PipColorOverlay extends PipContentOverlay { + private static final String TAG = PipColorOverlay.class.getSimpleName(); + private final Context mContext; public PipColorOverlay(Context context) { mContext = context; mLeash = new SurfaceControl.Builder(new SurfaceSession()) - .setCallsite("PipAnimation") - .setName(PipColorOverlay.class.getSimpleName()) + .setCallsite(TAG) + .setName(TAG) .setColorLayer() .build(); } @@ -88,7 +102,8 @@ public abstract class PipContentOverlay { } @Override - public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) { + public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction) { atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); } @@ -114,6 +129,8 @@ public abstract class PipContentOverlay { /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */ public static final class PipSnapshotOverlay extends PipContentOverlay { + private static final String TAG = PipSnapshotOverlay.class.getSimpleName(); + private final TaskSnapshot mSnapshot; private final Rect mSourceRectHint; @@ -121,8 +138,8 @@ public abstract class PipContentOverlay { mSnapshot = snapshot; mSourceRectHint = new Rect(sourceRectHint); mLeash = new SurfaceControl.Builder(new SurfaceSession()) - .setCallsite("PipAnimation") - .setName(PipSnapshotOverlay.class.getSimpleName()) + .setCallsite(TAG) + .setName(TAG) .build(); } @@ -143,7 +160,8 @@ public abstract class PipContentOverlay { } @Override - public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) { + public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction) { // Do nothing. Keep the snapshot till animation ends. } @@ -152,4 +170,113 @@ public abstract class PipContentOverlay { atomicTx.remove(mLeash); } } + + /** A {@link PipContentOverlay} shows app icon on solid color background. */ + public static final class PipAppIconOverlay extends PipContentOverlay { + private static final String TAG = PipAppIconOverlay.class.getSimpleName(); + private static final int APP_ICON_SIZE_DP = 48; + + private final Context mContext; + private final int mAppIconSizePx; + private final Rect mAppBounds; + private final Matrix mTmpTransform = new Matrix(); + private final float[] mTmpFloat9 = new float[9]; + + private Bitmap mBitmap; + + public PipAppIconOverlay(Context context, Rect appBounds, ActivityInfo activityInfo) { + mContext = context; + mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP, + context.getResources().getDisplayMetrics()); + mAppBounds = new Rect(appBounds); + mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(), + Bitmap.Config.ARGB_8888); + prepareAppIconOverlay(activityInfo); + mLeash = new SurfaceControl.Builder(new SurfaceSession()) + .setCallsite(TAG) + .setName(TAG) + .build(); + } + + @Override + public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { + tx.show(mLeash); + tx.setLayer(mLeash, Integer.MAX_VALUE); + tx.setBuffer(mLeash, mBitmap.getHardwareBuffer()); + tx.reparent(mLeash, parentLeash); + tx.apply(); + } + + @Override + public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction) { + mTmpTransform.reset(); + // Scale back the bitmap with the pivot point at center. + mTmpTransform.postScale( + (float) mAppBounds.width() / currentBounds.width(), + (float) mAppBounds.height() / currentBounds.height(), + mAppBounds.centerX(), + mAppBounds.centerY()); + atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9) + .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); + } + + @Override + public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { + atomicTx.remove(mLeash); + } + + @Override + public void detach(SurfaceControl.Transaction tx) { + super.detach(tx); + if (mBitmap != null && !mBitmap.isRecycled()) { + mBitmap.recycle(); + } + } + + private void prepareAppIconOverlay(ActivityInfo activityInfo) { + final Canvas canvas = new Canvas(); + canvas.setBitmap(mBitmap); + final TypedArray ta = mContext.obtainStyledAttributes(new int[] { + android.R.attr.colorBackground }); + try { + int colorAccent = ta.getColor(0, 0); + canvas.drawRGB( + Color.red(colorAccent), + Color.green(colorAccent), + Color.blue(colorAccent)); + } finally { + ta.recycle(); + } + final Drawable appIcon = loadActivityInfoIcon(activityInfo, + mContext.getResources().getConfiguration().densityDpi); + final Rect appIconBounds = new Rect( + mAppBounds.centerX() - mAppIconSizePx / 2, + mAppBounds.centerY() - mAppIconSizePx / 2, + mAppBounds.centerX() + mAppIconSizePx / 2, + mAppBounds.centerY() + mAppIconSizePx / 2); + appIcon.setBounds(appIconBounds); + appIcon.draw(canvas); + mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */); + } + + // Copied from com.android.launcher3.icons.IconProvider#loadActivityInfoIcon + private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) { + final int iconRes = ai.getIconResource(); + Drawable icon = null; + // Get the preferred density icon from the app's resources + if (density != 0 && iconRes != 0) { + try { + final Resources resources = mContext.getPackageManager() + .getResourcesForApplication(ai.applicationInfo); + icon = resources.getDrawableForDensity(iconRes, density); + } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) { } + } + // Get the default density icon + if (icon == null) { + icon = ai.loadIcon(mContext.getPackageManager()); + } + return icon; + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 8ba2583757cd..aad27b991db9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -62,6 +62,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.Log; import android.view.Choreographer; import android.view.Display; @@ -1568,7 +1569,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // Similar to auto-enter-pip transition, we use content overlay when there is no // source rect hint to enter PiP use bounds animation. if (sourceHintRect == null) { - animator.setColorContentOverlay(mContext); + if (SystemProperties.getBoolean( + "persist.wm.debug.enable_pip_app_icon_overlay", false)) { + animator.setAppIconContentOverlay( + mContext, currentBounds, mTaskInfo.topActivityInfo); + } else { + animator.setColorContentOverlay(mContext); + } } else { final TaskSnapshot snapshot = PipUtils.getTaskSnapshot( mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */); 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 83158ffafa7e..d9d10099e53a 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 @@ -50,6 +50,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; +import android.os.SystemProperties; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; @@ -792,7 +793,13 @@ public class PipTransition extends PipTransitionController { if (sourceHintRect == null) { // We use content overlay when there is no source rect hint to enter PiP use bounds // animation. - animator.setColorContentOverlay(mContext); + if (SystemProperties.getBoolean( + "persist.wm.debug.enable_pip_app_icon_overlay", false)) { + animator.setAppIconContentOverlay( + mContext, currentBounds, taskInfo.topActivityInfo); + } else { + animator.setColorContentOverlay(mContext); + } } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 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 21eeaa2cafb3..7cb5cf2ea177 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 @@ -28,6 +28,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +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.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; @@ -39,11 +42,8 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.PendingIntent; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Bundle; @@ -82,8 +82,8 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -318,10 +318,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator; } - public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) { - return mStageCoordinator.isValidToEnterSplitScreen(taskInfo); - } - @Nullable public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) { if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) { @@ -480,39 +476,54 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startShortcut(String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user) { - IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { - @Override - public void onAnimationStart(@WindowManager.TransitionOldType int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - final IRemoteAnimationFinishedCallback finishedCallback) { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to invoke onAnimationFinished", e); - } - final WindowContainerTransaction evictWct = new WindowContainerTransaction(); - mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct); - mSyncQueue.queue(evictWct); + if (options == null) options = new Bundle(); + final ActivityOptions activityOptions = ActivityOptions.fromBundle(options); + + if (samePackage(packageName, getPackageName(reverseSplitPosition(position)))) { + if (supportMultiInstancesSplit(packageName)) { + activityOptions.setApplyMultipleTaskFlagForShortcut(true); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else if (isSplitScreenVisible()) { + mStageCoordinator.switchSplitPosition("startShortcut"); + return; + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Cancel entering split as not supporting multi-instances"); + Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, + Toast.LENGTH_SHORT).show(); + return; } - @Override - public void onAnimationCancelled(boolean isKeyguardOccluded) { + } + + mStageCoordinator.startShortcut(packageName, shortcutId, position, + activityOptions.toBundle(), user); + } + + void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, + @Nullable Bundle options1, int taskId, @Nullable Bundle options2, + @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, + InstanceId instanceId) { + if (options1 == null) options1 = new Bundle(); + final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); + + final String packageName1 = shortcutInfo.getPackage(); + final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(shortcutInfo.getPackage())) { + activityOptions.setApplyMultipleTaskFlagForShortcut(true); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else { + taskId = INVALID_TASK_ID; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Cancel entering split as not supporting multi-instances"); + Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, + Toast.LENGTH_SHORT).show(); } - }; - options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, - null /* wct */); - RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper, - 0 /* duration */, 0 /* statusBarTransitionDelay */); - ActivityOptions activityOptions = ActivityOptions.fromBundle(options); - activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); - try { - LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); - launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */, - activityOptions.toBundle(), user); - } catch (ActivityNotFoundException e) { - Slog.e(TAG, "Failed to launch shortcut", e); } + + mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo, + activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter, + instanceId); } /** @@ -530,8 +541,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { Intent fillInIntent = null; - if (launchSameAppAdjacently(pendingIntent, taskId)) { - if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { + final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); + final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); @@ -551,8 +564,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent = null; - if (launchSameAppAdjacently(pendingIntent, taskId)) { - if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) { + final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); + final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); @@ -573,8 +588,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { Intent fillInIntent1 = null; Intent fillInIntent2 = null; - if (launchSameAppAdjacently(pendingIntent1, pendingIntent2)) { - if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) { + final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); + final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); fillInIntent2 = new Intent(); @@ -602,13 +619,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, if (fillInIntent == null) fillInIntent = new Intent(); fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); - if (launchSameAppAdjacently(position, intent)) { - final ComponentName launching = intent.getIntent().getComponent(); - if (supportMultiInstancesSplit(launching)) { + final String packageName1 = SplitScreenUtils.getPackageName(intent); + final String packageName2 = getPackageName(reverseSplitPosition(position)); + if (SplitScreenUtils.samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { // To prevent accumulating large number of instances in the background, reuse task // in the background with priority. final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional - .map(recentTasks -> recentTasks.findTaskInBackground(launching)) + .map(recentTasks -> recentTasks.findTaskInBackground( + intent.getIntent().getComponent())) .orElse(null); if (taskInfo != null) { startTask(taskInfo.taskId, position, options); @@ -636,63 +655,32 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.startIntent(intent, fillInIntent, position, options); } + /** Retrieve package name of a specific split position if split screen is activated, otherwise + * returns the package name of the top running task. */ @Nullable - private String getPackageName(Intent intent) { - if (intent == null || intent.getComponent() == null) { - return null; - } - return intent.getComponent().getPackageName(); - } - - private boolean launchSameAppAdjacently(@SplitPosition int position, - PendingIntent pendingIntent) { - ActivityManager.RunningTaskInfo adjacentTaskInfo = null; + private String getPackageName(@SplitPosition int position) { + ActivityManager.RunningTaskInfo taskInfo; if (isSplitScreenVisible()) { - adjacentTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); + taskInfo = getTaskInfo(position); } else { - adjacentTaskInfo = mRecentTasksOptional - .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null); - if (!isValidToEnterSplitScreen(adjacentTaskInfo)) { - return false; + taskInfo = mRecentTasksOptional + .map(recentTasks -> recentTasks.getTopRunningTask()) + .orElse(null); + if (!isValidToSplit(taskInfo)) { + return null; } } - if (adjacentTaskInfo == null) { - return false; - } - - final String targetPackageName = getPackageName(pendingIntent.getIntent()); - final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent); - return targetPackageName != null && targetPackageName.equals(adjacentPackageName); - } - - private boolean launchSameAppAdjacently(PendingIntent pendingIntent, int taskId) { - final ActivityManager.RunningTaskInfo adjacentTaskInfo = - mTaskOrganizer.getRunningTaskInfo(taskId); - if (adjacentTaskInfo == null) { - return false; - } - final String targetPackageName = getPackageName(pendingIntent.getIntent()); - final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent); - return targetPackageName != null && targetPackageName.equals(adjacentPackageName); - } - - private boolean launchSameAppAdjacently(PendingIntent pendingIntent1, - PendingIntent pendingIntent2) { - final String targetPackageName = getPackageName(pendingIntent1.getIntent()); - final String adjacentPackageName = getPackageName(pendingIntent2.getIntent()); - return targetPackageName != null && targetPackageName.equals(adjacentPackageName); + return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null; } @VisibleForTesting - /** Returns {@code true} if the component supports multi-instances split. */ - boolean supportMultiInstancesSplit(@Nullable ComponentName launching) { - if (launching == null) return false; - - final String packageName = launching.getPackageName(); - for (int i = 0; i < mAppsSupportMultiInstances.length; i++) { - if (mAppsSupportMultiInstances[i].equals(packageName)) { - return true; + boolean supportMultiInstancesSplit(String packageName) { + if (packageName != null) { + for (int i = 0; i < mAppsSupportMultiInstances.length; i++) { + if (mAppsSupportMultiInstances[i].equals(packageName)) { + return true; + } } } @@ -1011,7 +999,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTaskWithLegacyTransition", (controller) -> - controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition( + controller.startShortcutAndTaskWithLegacyTransition( shortcutInfo, options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId)); } 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 39cf5f1b95bd..5a9170b48ede 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 @@ -36,12 +36,11 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; -import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; -import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; 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; @@ -76,8 +75,10 @@ import android.app.ActivityOptions; import android.app.IActivityTaskManager; import android.app.PendingIntent; import android.app.WindowConfiguration; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; +import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.graphics.Rect; @@ -87,6 +88,7 @@ import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.util.Slog; import android.view.Choreographer; @@ -370,7 +372,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, int sideStagePosition; if (stageType == STAGE_TYPE_MAIN) { targetStage = mMainStage; - sideStagePosition = SplitLayout.reversePosition(stagePosition); + sideStagePosition = reverseSplitPosition(stagePosition); } else if (stageType == STAGE_TYPE_SIDE) { targetStage = mSideStage; sideStagePosition = stagePosition; @@ -428,6 +430,72 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mLogger; } + void startShortcut(String packageName, String shortcutId, @SplitPosition int position, + Bundle options, UserHandle user) { + final boolean isEnteringSplit = !isSplitActive(); + + IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + final IRemoteAnimationFinishedCallback finishedCallback) { + boolean openingToSide = false; + if (apps != null) { + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING + && mSideStage.containsTask(apps[i].taskId)) { + openingToSide = true; + break; + } + } + } + + if (isEnteringSplit && !openingToSide) { + mMainExecutor.execute(() -> exitSplitScreen( + mSideStage.getChildCount() == 0 ? mMainStage : mSideStage, + EXIT_REASON_UNKNOWN)); + } + + if (finishedCallback != null) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "Error finishing legacy transition: ", e); + } + } + + if (!isEnteringSplit && openingToSide) { + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + prepareEvictNonOpeningChildTasks(position, apps, evictWct); + mSyncQueue.queue(evictWct); + } + } + @Override + public void onAnimationCancelled(boolean isKeyguardOccluded) { + if (isEnteringSplit) { + mMainExecutor.execute(() -> exitSplitScreen( + mSideStage.getChildCount() == 0 ? mMainStage : mSideStage, + EXIT_REASON_UNKNOWN)); + } + } + }; + options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, + null /* wct */); + RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper, + 0 /* duration */, 0 /* statusBarTransitionDelay */); + ActivityOptions activityOptions = ActivityOptions.fromBundle(options); + activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); + try { + LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); + launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */, + activityOptions.toBundle(), user); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "Failed to launch shortcut", e); + } + } + /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { @@ -899,7 +967,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, case STAGE_TYPE_MAIN: { if (position != SPLIT_POSITION_UNDEFINED) { // Set the side stage opposite of what we want to the main stage. - setSideStagePosition(SplitLayout.reversePosition(position), wct); + setSideStagePosition(reverseSplitPosition(position), wct); } else { position = getMainStagePosition(); } @@ -923,7 +991,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @SplitPosition int getMainStagePosition() { - return SplitLayout.reversePosition(mSideStagePosition); + return reverseSplitPosition(mSideStagePosition); } int getTaskId(@SplitPosition int splitPosition) { @@ -950,7 +1018,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash, insets -> { WindowContainerTransaction wct = new WindowContainerTransaction(); - setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct); + setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct); mSyncQueue.queue(wct); mSyncQueue.runInSync(st -> { updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */); @@ -1694,12 +1762,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) { - return taskInfo.supportsMultiWindow - && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()) - && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()); - } - @Override public void onSnappedToDismiss(boolean bottomOrRight, int reason) { final boolean mainStageToTop = @@ -2108,7 +2170,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Use normal animations. return false; - } else if (mMixedHandler != null && hasDisplayChange(info)) { + } else if (mMixedHandler != null && Transitions.hasDisplayChange(info)) { // A display-change has been un-expectedly inserted into the transition. Redirect // handling to the mixed-handler to deal with splitting it up. if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, @@ -2151,15 +2213,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - private boolean hasDisplayChange(TransitionInfo info) { - boolean has = false; - for (int iC = 0; iC < info.getChanges().size() && !has; ++iC) { - final TransitionInfo.Change change = info.getChanges().get(iC); - has = change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0; - } - return has; - } - /** Called to clean-up state and do house-keeping after the animation is done. */ public void onTransitionAnimationComplete() { // If still playing, let it finish. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index b4e05848882c..02f19ebdb758 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -93,6 +93,11 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (!Transitions.SHELL_TRANSITIONS_ROTATION && Transitions.hasDisplayChange(info)) { + // Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some + // operations of the start transaction may be ignored. + return false; + } RemoteTransition pendingRemote = mRequestedRemotes.get(transition); if (pendingRemote == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have " diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 44d6a0de1366..b2f61c231911 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; +import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_NO_ANIMATION; @@ -328,6 +329,17 @@ public class Transitions implements RemoteCallable<Transitions> { return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; } + /** Returns {@code true} if the transition has a display change. */ + public static boolean hasDisplayChange(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { + return true; + } + } + return false; + } + /** * Sets up visibility/alpha/transforms to resemble the starting state of an animation. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index ea3af9d96aa4..d0e26019f9bf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -27,8 +27,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -201,7 +199,6 @@ public class SplitScreenControllerTests extends ShellTestCase { ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); - doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any()); mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null); @@ -222,7 +219,6 @@ public class SplitScreenControllerTests extends ShellTestCase { ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); - doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any()); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any()); diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 28496f135890..c5202dcd4879 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -108,133 +108,148 @@ public final class MediaRoute2Info implements Parcelable { public static final int PLAYBACK_VOLUME_VARIABLE = 1; /** @hide */ - @IntDef({ - TYPE_UNKNOWN, TYPE_BUILTIN_SPEAKER, TYPE_WIRED_HEADSET, - TYPE_WIRED_HEADPHONES, TYPE_BLUETOOTH_A2DP, TYPE_HDMI, TYPE_USB_DEVICE, - TYPE_USB_ACCESSORY, TYPE_DOCK, TYPE_USB_HEADSET, TYPE_HEARING_AID, TYPE_BLE_HEADSET, - TYPE_REMOTE_TV, TYPE_REMOTE_SPEAKER, TYPE_GROUP}) + @IntDef( + prefix = {"TYPE_"}, + value = { + TYPE_UNKNOWN, + TYPE_BUILTIN_SPEAKER, + TYPE_WIRED_HEADSET, + TYPE_WIRED_HEADPHONES, + TYPE_BLUETOOTH_A2DP, + TYPE_HDMI, + TYPE_USB_DEVICE, + TYPE_USB_ACCESSORY, + TYPE_DOCK, + TYPE_USB_HEADSET, + TYPE_HEARING_AID, + TYPE_BLE_HEADSET, + TYPE_REMOTE_TV, + TYPE_REMOTE_SPEAKER, + TYPE_REMOTE_AUDIO_VIDEO_RECEIVER, + TYPE_GROUP + }) @Retention(RetentionPolicy.SOURCE) public @interface Type {} /** - * The default route type indicating the type is unknown. + * Indicates the route's type is unknown or undefined. * * @see #getType - * @hide */ public static final int TYPE_UNKNOWN = 0; /** - * A route type describing the speaker system (i.e. a mono speaker or stereo speakers) built - * in a device. + * Indicates the route is the speaker system (i.e. a mono speaker or stereo speakers) built into + * the device. * * @see #getType - * @hide */ public static final int TYPE_BUILTIN_SPEAKER = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; /** - * A route type describing a headset, which is the combination of a headphones and microphone. + * Indicates the route is a headset, which is the combination of a headphones and a microphone. * * @see #getType - * @hide */ public static final int TYPE_WIRED_HEADSET = AudioDeviceInfo.TYPE_WIRED_HEADSET; /** - * A route type describing a pair of wired headphones. + * Indicates the route is a pair of wired headphones. * * @see #getType - * @hide */ public static final int TYPE_WIRED_HEADPHONES = AudioDeviceInfo.TYPE_WIRED_HEADPHONES; /** - * A route type indicating the presentation of the media is happening - * on a bluetooth device such as a bluetooth speaker. + * Indicates the route is a bluetooth device, such as a bluetooth speaker or headphones. * * @see #getType - * @hide */ public static final int TYPE_BLUETOOTH_A2DP = AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; /** - * A route type describing an HDMI connection. + * Indicates the route is an HDMI connection. * * @see #getType - * @hide */ public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI; /** - * A route type describing a USB audio device. + * Indicates the route is a USB audio device. * * @see #getType - * @hide */ public static final int TYPE_USB_DEVICE = AudioDeviceInfo.TYPE_USB_DEVICE; /** - * A route type describing a USB audio device in accessory mode. + * Indicates the route is a USB audio device in accessory mode. * * @see #getType - * @hide */ public static final int TYPE_USB_ACCESSORY = AudioDeviceInfo.TYPE_USB_ACCESSORY; /** - * A route type describing the audio device associated with a dock. + * Indicates the route is the audio device associated with a dock. * * @see #getType - * @hide */ public static final int TYPE_DOCK = AudioDeviceInfo.TYPE_DOCK; /** - * A device type describing a USB audio headset. + * Indicates the route is a USB audio headset. * * @see #getType - * @hide */ public static final int TYPE_USB_HEADSET = AudioDeviceInfo.TYPE_USB_HEADSET; /** - * A route type describing a Hearing Aid. + * Indicates the route is a hearing aid. * * @see #getType - * @hide */ public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID; /** - * A route type describing a BLE HEADSET. + * Indicates the route is a Bluetooth Low Energy (BLE) HEADSET. * * @see #getType - * @hide */ public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET; /** - * A route type indicating the presentation of the media is happening on a TV. + * Indicates the route is a remote TV. + * + * <p>A remote device uses a routing protocol managed by the application, as opposed to the + * routing being done by the system. * * @see #getType - * @hide */ public static final int TYPE_REMOTE_TV = 1001; /** - * A route type indicating the presentation of the media is happening on a speaker. + * Indicates the route is a remote speaker. + * + * <p>A remote device uses a routing protocol managed by the application, as opposed to the + * routing being done by the system. * * @see #getType - * @hide */ public static final int TYPE_REMOTE_SPEAKER = 1002; /** - * A route type indicating the presentation of the media is happening on multiple devices. + * Indicates the route is a remote Audio/Video Receiver (AVR). + * + * <p>A remote device uses a routing protocol managed by the application, as opposed to the + * routing being done by the system. + * + * @see #getType + */ + public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; + + /** + * Indicates the route is a group of devices. * * @see #getType - * @hide */ public static final int TYPE_GROUP = 2000; @@ -436,16 +451,23 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Gets the type of this route. + * Returns the type of this route. * - * @return The type of this route: - * {@link #TYPE_UNKNOWN}, - * {@link #TYPE_BUILTIN_SPEAKER}, {@link #TYPE_WIRED_HEADSET}, {@link #TYPE_WIRED_HEADPHONES}, - * {@link #TYPE_BLUETOOTH_A2DP}, {@link #TYPE_HDMI}, {@link #TYPE_DOCK}, - * {@Link #TYPE_USB_DEVICE}, {@link #TYPE_USB_ACCESSORY}, {@link #TYPE_USB_HEADSET} - * {@link #TYPE_HEARING_AID}, - * {@link #TYPE_REMOTE_TV}, {@link #TYPE_REMOTE_SPEAKER}, {@link #TYPE_GROUP}. - * @hide + * @see #TYPE_UNKNOWN + * @see #TYPE_BUILTIN_SPEAKER + * @see #TYPE_WIRED_HEADSET + * @see #TYPE_WIRED_HEADPHONES + * @see #TYPE_BLUETOOTH_A2DP + * @see #TYPE_HDMI + * @see #TYPE_DOCK + * @see #TYPE_USB_DEVICE + * @see #TYPE_USB_ACCESSORY + * @see #TYPE_USB_HEADSET + * @see #TYPE_HEARING_AID + * @see #TYPE_REMOTE_TV + * @see #TYPE_REMOTE_SPEAKER + * @see #TYPE_REMOTE_AUDIO_VIDEO_RECEIVER + * @see #TYPE_GROUP */ @Type public int getType() { @@ -657,6 +679,7 @@ public final class MediaRoute2Info implements Parcelable { pw.println(indent + "mId=" + mId); pw.println(indent + "mName=" + mName); pw.println(indent + "mFeatures=" + mFeatures); + pw.println(indent + "mType=" + getDeviceTypeString(mType)); pw.println(indent + "mIsSystem=" + mIsSystem); pw.println(indent + "mIconUri=" + mIconUri); pw.println(indent + "mDescription=" + mDescription); @@ -787,6 +810,42 @@ public final class MediaRoute2Info implements Parcelable { dest.writeString8Array(mAllowedPackages.toArray(new String[0])); } + private static String getDeviceTypeString(@Type int deviceType) { + switch (deviceType) { + case TYPE_BUILTIN_SPEAKER: + return "BUILTIN_SPEAKER"; + case TYPE_WIRED_HEADSET: + return "WIRED_HEADSET"; + case TYPE_WIRED_HEADPHONES: + return "WIRED_HEADPHONES"; + case TYPE_BLUETOOTH_A2DP: + return "BLUETOOTH_A2DP"; + case TYPE_HDMI: + return "HDMI"; + case TYPE_DOCK: + return "DOCK"; + case TYPE_USB_DEVICE: + return "USB_DEVICE"; + case TYPE_USB_ACCESSORY: + return "USB_ACCESSORY"; + case TYPE_USB_HEADSET: + return "USB_HEADSET"; + case TYPE_HEARING_AID: + return "HEARING_AID"; + case TYPE_REMOTE_TV: + return "REMOTE_TV"; + case TYPE_REMOTE_SPEAKER: + return "REMOTE_SPEAKER"; + case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER: + return "REMOTE_AUDIO_VIDEO_RECEIVER"; + case TYPE_GROUP: + return "GROUP"; + case TYPE_UNKNOWN: + default: + return TextUtils.formatSimple("UNKNOWN(%d)", deviceType); + } + } + /** * Builder for {@link MediaRoute2Info media route info}. */ @@ -932,7 +991,8 @@ public final class MediaRoute2Info implements Parcelable { /** * Sets the route's type. - * @hide + * + * @see MediaRoute2Info#getType() */ @NonNull public Builder setType(@Type int type) { diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java index ab785367a507..6caedda56689 100644 --- a/media/java/android/media/RouteListingPreference.java +++ b/media/java/android/media/RouteListingPreference.java @@ -259,7 +259,7 @@ public final class RouteListingPreference implements Parcelable { @IntDef( flag = true, prefix = {"FLAG_"}, - value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE, FLAG_ONGOING_SESSION_MANAGED}) + value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED, FLAG_ONGOING_SESSION_MANAGED}) public @interface Flags {} /** @@ -292,7 +292,7 @@ public final class RouteListingPreference implements Parcelable { * number supported by the UI, the routes listed first in {@link * RouteListingPreference#getItems()} will take priority. */ - public static final int FLAG_SUGGESTED_ROUTE = 1 << 2; + public static final int FLAG_SUGGESTED = 1 << 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -399,7 +399,7 @@ public final class RouteListingPreference implements Parcelable { * Returns the flags associated to the route that corresponds to this item. * * @see #FLAG_ONGOING_SESSION - * @see #FLAG_SUGGESTED_ROUTE + * @see #FLAG_SUGGESTED */ @Flags public int getFlags() { diff --git a/media/java/android/media/tv/AdBuffer.java b/media/java/android/media/tv/AdBuffer.java index ed44508d2262..230d763a948d 100644 --- a/media/java/android/media/tv/AdBuffer.java +++ b/media/java/android/media/tv/AdBuffer.java @@ -24,9 +24,8 @@ import android.os.SharedMemory; /** * Buffer for advertisement data. - * @hide */ -public class AdBuffer implements Parcelable { +public final class AdBuffer implements Parcelable { private final int mId; @NonNull private final String mMimeType; @@ -60,6 +59,8 @@ public class AdBuffer implements Parcelable { /** * Gets corresponding AD request ID. + * + * @return The ID of the ad request */ public int getId() { return mId; @@ -67,6 +68,8 @@ public class AdBuffer implements Parcelable { /** * Gets the mime type of the data. + * + * @return The mime type of the data. */ @NonNull public String getMimeType() { @@ -74,7 +77,17 @@ public class AdBuffer implements Parcelable { } /** - * Gets the shared memory which stores the data. + * Gets the {@link SharedMemory} which stores the data. + * + * <p> Information on how the data in this buffer is formatted can be found using + * {@link AdRequest#getMetadata()} + * <p> This data lives in a {@link SharedMemory} instance because of the + * potentially large amount of data needed to store the ad. This optimizes the + * data communication between the ad data source and the service responsible for + * its display. + * + * @see SharedMemory#create(String, int) + * @return The {@link SharedMemory} that stores the data for this ad buffer. */ @NonNull public SharedMemory getSharedMemory() { @@ -82,28 +95,38 @@ public class AdBuffer implements Parcelable { } /** - * Gets the offset of the buffer. + * Gets the offset into the shared memory to begin mapping. + * + * @see SharedMemory#map(int, int, int) + * @return The offset of this ad buffer in the shared memory in bytes. */ public int getOffset() { return mOffset; } /** - * Gets the data length. + * Gets the data length of this ad buffer. + * + * @return The data length of this ad buffer in bytes. */ public int getLength() { return mLength; } /** - * Gets the presentation time in microseconds. + * Gets the presentation time. + * + * @return The presentation time in microseconds. */ public long getPresentationTimeUs() { return mPresentationTimeUs; } /** - * Gets the flags. + * Gets the buffer flags for this ad buffer. + * + * @see android.media.MediaCodec + * @return The buffer flags for this ad buffer. */ @BufferFlag public int getFlags() { diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java index 60dfc5e56dac..d8cddfcbcb9e 100644 --- a/media/java/android/media/tv/AdRequest.java +++ b/media/java/android/media/tv/AdRequest.java @@ -79,7 +79,6 @@ public final class AdRequest implements Parcelable { mediaFileType, metadata); } - /** @hide */ public AdRequest(int id, @RequestType int requestType, @Nullable Uri uri, long startTime, long stopTime, long echoInterval, @NonNull Bundle metadata) { this(id, requestType, null, uri, startTime, stopTime, echoInterval, null, metadata); @@ -153,7 +152,6 @@ public final class AdRequest implements Parcelable { * * @return The URI of the AD media. Can be {@code null} for {@link #REQUEST_TYPE_STOP} or a file * descriptor is used. - * @hide */ @Nullable public Uri getUri() { diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java index a15e8c180d50..7ec4eb27d31d 100644 --- a/media/java/android/media/tv/AdResponse.java +++ b/media/java/android/media/tv/AdResponse.java @@ -43,7 +43,6 @@ public final class AdResponse implements Parcelable { public static final int RESPONSE_TYPE_FINISHED = 2; public static final int RESPONSE_TYPE_STOPPED = 3; public static final int RESPONSE_TYPE_ERROR = 4; - /** @hide */ public static final int RESPONSE_TYPE_BUFFERING = 5; public static final @NonNull Parcelable.Creator<AdResponse> CREATOR = diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 7a4d988df201..8166114a90bf 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1005,9 +1005,10 @@ public abstract class TvInputService extends Service { /** * Notifies the advertisement buffer is consumed. - * @hide + * + * @param buffer the {@link AdBuffer} that was consumed. */ - public void notifyAdBufferConsumed(AdBuffer buffer) { + public void notifyAdBufferConsumed(@NonNull AdBuffer buffer) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override @@ -1320,10 +1321,11 @@ public abstract class TvInputService extends Service { } /** - * Called when advertisement buffer is ready. - * @hide + * Called when an advertisement buffer is ready for playback. + * + * @param buffer The {@link AdBuffer} that became ready for playback. */ - public void onAdBuffer(AdBuffer buffer) { + public void onAdBuffer(@NonNull AdBuffer buffer) { } /** diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index cdaa3e50e17d..8b85fa189ac9 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -896,10 +896,10 @@ public abstract class TvInteractiveAppService extends Service { /** * Called when an advertisement buffer is consumed. - * @hide + * + * @param buffer The {@link AdBuffer} that was consumed. */ - public void onAdBufferConsumed(AdBuffer buffer) { - + public void onAdBufferConsumed(@NonNull AdBuffer buffer) { } /** @@ -1919,10 +1919,11 @@ public abstract class TvInteractiveAppService extends Service { /** * Notifies when the advertisement buffer is filled and ready to be read. - * @hide + * + * @param buffer The {@link AdBuffer} to be received */ @CallSuper - public void notifyAdBuffer(AdBuffer buffer) { + public void notifyAdBuffer(@NonNull AdBuffer buffer) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @Override diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml index ab2d815063eb..6c463e18ef11 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml @@ -22,7 +22,7 @@ android:orientation="horizontal" android:paddingStart="32dp" android:paddingEnd="32dp" - android:paddingBottom="14dp"> + android:paddingTop="12dp"> <ImageView android:id="@+id/permission_icon" diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 7397688539b2..b8427613d8ce 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -20,7 +20,7 @@ <string name="app_label">Companion Device Manager</string> <!-- Title of the device association confirmation dialog. --> - <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to access your <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string> + <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to access <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string> <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= --> @@ -31,10 +31,10 @@ <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by <strong><xliff:g id="app_name" example="Android Wear">%2$s</xliff:g></strong></string> <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] --> - <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string> + <string name="summary_watch">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string> <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] --> - <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to interact with these permissions:</string> + <string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:</string> <!-- TODO(b/256140614) To replace all glasses related strings with final versions --> <!-- ================= DEVICE_PROFILE_GLASSES ================= --> @@ -43,10 +43,10 @@ <string name="profile_name_glasses">glasses</string> <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] --> - <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string> + <string name="summary_glasses">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string> <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] --> - <string name="summary_glasses_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string> + <string name="summary_glasses_single_device">The app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string> <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= --> @@ -99,7 +99,10 @@ <string name="profile_name_generic">device</string> <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] --> - <string name="summary_generic"></string> + <string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>.</string> + + <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] --> + <string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device.</string> <!-- ================= Buttons ================= --> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index c5ed5c9680a8..918f9c681c5a 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -546,17 +546,16 @@ public class CompanionDeviceActivity extends FragmentActivity implements } if (deviceProfile == null) { - // Summary is not needed for null profile. - mSummary.setVisibility(View.GONE); + summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName); mConstraintList.setVisibility(View.GONE); } else { + summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile), + getString(PROFILES_NAME.get(deviceProfile)), appLabel); mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile)); setupPermissionList(); } title = getHtmlFromResources(this, TITLES.get(deviceProfile), appLabel, deviceName); - summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile), - getString(PROFILES_NAME.get(deviceProfile)), appLabel); profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile)); mTitle.setText(title); @@ -586,7 +585,6 @@ public class CompanionDeviceActivity extends FragmentActivity implements if (deviceProfile == null) { summary = getHtmlFromResources(this, summaryResourceId); - mSummary.setVisibility(View.GONE); } else { summary = getHtmlFromResources(this, summaryResourceId, profileName, appLabel); } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java index 6f5f4fe91c99..e3fd3545ca4b 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java @@ -88,7 +88,7 @@ final class CompanionDeviceResources { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch_single_device); map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_single_device); - map.put(null, R.string.summary_generic); + map.put(null, R.string.summary_generic_single_device); SUMMARIES = unmodifiableMap(map); } diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index d69097199712..49ac482699cf 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -13,6 +13,10 @@ <string name="string_more_options">More options</string> <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] --> <string name="string_learn_more">Learn more</string> + <!-- This is a label for content description for show password icon button. --> + <string name="content_description_show_password">Show password</string> + <!-- This is a label for content description for hide password icon button. --> + <string name="content_description_hide_password">Hide password</string> <!-- This string introduces passkeys to the users for the first time they use this method. Tip: to avoid gendered language patterns, this header could be translated as if the original string were "More safety with passkeys". [CHAR LIMIT=200] --> <string name="passkey_creation_intro_title">Safer with passkeys</string> <!-- This string highlight passkey benefits related with the password. [CHAR LIMIT=200] --> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index c66ea5e1af9e..6ea1d8d522f9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -64,7 +64,7 @@ class CredentialManagerRepo( requestInfo = intent.extras?.getParcelable( RequestInfo.EXTRA_REQUEST_INFO, RequestInfo::class.java - ) ?: testCreatePasskeyRequestInfo() + ) ?: testCreatePasswordRequestInfo() providerEnabledList = when (requestInfo.type) { RequestInfo.TYPE_CREATE -> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index d8420cdd7ff1..15acd8c458a2 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -198,7 +198,7 @@ class GetFlowUtils { credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), - icon = credentialEntry.icon.loadDrawable(context), + icon = credentialEntry.icon?.loadDrawable(context), lastUsedTimeMillis = credentialEntry.lastUsedTime, )) } @@ -213,7 +213,7 @@ class GetFlowUtils { credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), - icon = credentialEntry.icon.loadDrawable(context), + icon = credentialEntry.icon?.loadDrawable(context), lastUsedTimeMillis = credentialEntry.lastUsedTime, )) } @@ -228,7 +228,7 @@ class GetFlowUtils { credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.title.toString(), displayName = credentialEntry.subtitle?.toString(), - icon = credentialEntry.icon.loadDrawable(context), + icon = credentialEntry.icon?.loadDrawable(context), lastUsedTimeMillis = credentialEntry.lastUsedTime, )) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt index d0271abccabd..984057a1c960 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt @@ -16,11 +16,23 @@ package com.android.credentialmanager.common.ui +import com.android.credentialmanager.R +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Visibility +import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @Composable fun ActionButton(text: String, onClick: () -> Unit) { @@ -32,4 +44,27 @@ fun ActionButton(text: String, onClick: () -> Unit) { ) { Text(text = text) } +} + +@Composable +fun ToggleVisibilityButton(modifier: Modifier = Modifier, onToggle: (Boolean) -> Unit) { + // default state is visibility off + val toggleState: MutableState<Boolean> = remember { mutableStateOf(false) } + + IconButton( + modifier = modifier, + onClick = { + toggleState.value = !toggleState.value + onToggle(toggleState.value) + } + ) { + Icon( + imageVector = if (toggleState.value) + Icons.Outlined.Visibility else Icons.Outlined.VisibilityOff, + contentDescription = if (toggleState.value) + stringResource(R.string.content_description_show_password) else + stringResource(R.string.content_description_hide_password), + tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant + ) + } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index f8d008e3e4af..0b9e57821d12 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn @@ -26,12 +28,17 @@ import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap @@ -48,6 +55,7 @@ import com.android.credentialmanager.common.ui.TextOnSurface import com.android.credentialmanager.common.ui.TextSecondary import com.android.credentialmanager.common.ui.TextOnSurfaceVariant import com.android.credentialmanager.common.ui.ContainerCard +import com.android.credentialmanager.common.ui.ToggleVisibilityButton import com.android.credentialmanager.ui.theme.EntryShape import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @@ -851,12 +859,38 @@ fun PrimaryCreateOptionRow( style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(top = 16.dp, start = 5.dp), ) - TextSecondary( + Row(modifier = Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 16.dp, + start = 5.dp), + verticalAlignment = Alignment.CenterVertically) { + val visualTransformation = remember { PasswordVisualTransformation() } // This subtitle would never be null for create password - text = requestDisplayInfo.subtitle ?: "", - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) + val originalPassword by remember { + mutableStateOf(requestDisplayInfo.subtitle ?: "") + } + val displayedPassword = remember { + mutableStateOf( + visualTransformation.filter( + AnnotatedString(originalPassword) + ).text.text + ) + } + TextSecondary( + text = displayedPassword.value, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 4.dp, bottom = 4.dp), + ) + + ToggleVisibilityButton(modifier = Modifier.padding(start = 4.dp) + .height(24.dp).width(24.dp), onToggle = { + if (it) { + displayedPassword.value = originalPassword + } else { + displayedPassword.value = visualTransformation.filter( + AnnotatedString(originalPassword) + ).text.text + } + }) + } } CredentialType.UNKNOWN -> { if (requestDisplayInfo.subtitle != null) { diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp index c35fb3b17880..4b4cfb7f38dc 100644 --- a/packages/SettingsLib/ActivityEmbedding/Android.bp +++ b/packages/SettingsLib/ActivityEmbedding/Android.bp @@ -30,5 +30,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.permission", + "com.android.healthconnect", ], } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt index 4b73e948e1e6..b92729dab7c0 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -24,10 +24,6 @@ import androidx.compose.runtime.ProvidedValue import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.remember import com.android.settingslib.spa.framework.compose.LocalNavController -import com.android.settingslib.spa.framework.util.genEntryId - -private const val INJECT_ENTRY_NAME = "INJECT" -private const val ROOT_ENTRY_NAME = "ROOT" interface EntryData { val pageId: String? @@ -164,143 +160,3 @@ data class SettingsEntry( } } } - -/** - * The helper to build a Settings Entry instance. - */ -class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) { - private var displayName = name - private var fromPage: SettingsPage? = null - private var toPage: SettingsPage? = null - - // Attributes - private var isAllowSearch: Boolean = false - private var isSearchDataDynamic: Boolean = false - private var hasMutableStatus: Boolean = false - private var hasSliceSupport: Boolean = false - - // Functions - private var uiLayoutFn: UiLayerRenderer = { } - private var statusDataFn: StatusDataGetter = { null } - private var searchDataFn: SearchDataGetter = { null } - private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null } - - fun build(): SettingsEntry { - val page = fromPage ?: owner - val isEnabled = page.isEnabled() - return SettingsEntry( - id = genEntryId(name, owner, fromPage, toPage), - name = name, - owner = owner, - displayName = displayName, - - // linking data - fromPage = fromPage, - toPage = toPage, - - // attributes - // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately - isAllowSearch = isEnabled && isAllowSearch, - isSearchDataDynamic = isSearchDataDynamic, - hasMutableStatus = hasMutableStatus, - hasSliceSupport = isEnabled && hasSliceSupport, - - // functions - statusDataImpl = statusDataFn, - searchDataImpl = searchDataFn, - sliceDataImpl = sliceDataFn, - uiLayoutImpl = uiLayoutFn, - ) - } - - fun setDisplayName(displayName: String): SettingsEntryBuilder { - this.displayName = displayName - return this - } - - fun setLink( - fromPage: SettingsPage? = null, - toPage: SettingsPage? = null - ): SettingsEntryBuilder { - if (fromPage != null) this.fromPage = fromPage - if (toPage != null) this.toPage = toPage - return this - } - - fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder { - this.isSearchDataDynamic = isDynamic - return this - } - - fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder { - this.hasMutableStatus = hasMutableStatus - return this - } - - fun setMacro(fn: (arguments: Bundle?) -> EntryMacro): SettingsEntryBuilder { - setStatusDataFn { fn(it).getStatusData() } - setSearchDataFn { fn(it).getSearchData() } - setUiLayoutFn { - val macro = remember { fn(it) } - macro.UiLayout() - } - return this - } - - fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder { - this.statusDataFn = fn - return this - } - - fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder { - this.searchDataFn = fn - this.isAllowSearch = true - return this - } - - fun clearSearchDataFn(): SettingsEntryBuilder { - this.searchDataFn = { null } - this.isAllowSearch = false - return this - } - - fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder { - this.sliceDataFn = fn - this.hasSliceSupport = true - return this - } - - fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder { - this.uiLayoutFn = fn - return this - } - - companion object { - fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder { - return SettingsEntryBuilder(entryName, owner) - } - - fun createLinkFrom(entryName: String, owner: SettingsPage): SettingsEntryBuilder { - return create(entryName, owner).setLink(fromPage = owner) - } - - fun createLinkTo(entryName: String, owner: SettingsPage): SettingsEntryBuilder { - return create(entryName, owner).setLink(toPage = owner) - } - - fun create(owner: SettingsPage, entryName: String, displayName: String? = null): - SettingsEntryBuilder { - return SettingsEntryBuilder(entryName, owner).setDisplayName(displayName ?: entryName) - } - - fun createInject(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder { - val name = displayName ?: "${INJECT_ENTRY_NAME}_${owner.displayName}" - return createLinkTo(INJECT_ENTRY_NAME, owner).setDisplayName(name) - } - - fun createRoot(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder { - val name = displayName ?: "${ROOT_ENTRY_NAME}_${owner.displayName}" - return createLinkTo(ROOT_ENTRY_NAME, owner).setDisplayName(name) - } - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt new file mode 100644 index 000000000000..67f9ea52a40d --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt @@ -0,0 +1,165 @@ +/* + * 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.spa.framework.common + +import android.net.Uri +import android.os.Bundle +import androidx.compose.runtime.remember +import com.android.settingslib.spa.framework.util.genEntryId + +private const val INJECT_ENTRY_NAME = "INJECT" +private const val ROOT_ENTRY_NAME = "ROOT" + +/** + * The helper to build a Settings Entry instance. + */ +class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) { + private var displayName = name + private var fromPage: SettingsPage? = null + private var toPage: SettingsPage? = null + + // Attributes + private var isAllowSearch: Boolean = false + private var isSearchDataDynamic: Boolean = false + private var hasMutableStatus: Boolean = false + private var hasSliceSupport: Boolean = false + + // Functions + private var uiLayoutFn: UiLayerRenderer = { } + private var statusDataFn: StatusDataGetter = { null } + private var searchDataFn: SearchDataGetter = { null } + private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null } + + fun build(): SettingsEntry { + val page = fromPage ?: owner + val isEnabled = page.isEnabled() + return SettingsEntry( + id = genEntryId(name, owner, fromPage, toPage), + name = name, + owner = owner, + displayName = displayName, + + // linking data + fromPage = fromPage, + toPage = toPage, + + // attributes + // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately + isAllowSearch = isEnabled && isAllowSearch, + isSearchDataDynamic = isSearchDataDynamic, + hasMutableStatus = hasMutableStatus, + hasSliceSupport = isEnabled && hasSliceSupport, + + // functions + statusDataImpl = statusDataFn, + searchDataImpl = searchDataFn, + sliceDataImpl = sliceDataFn, + uiLayoutImpl = uiLayoutFn, + ) + } + + fun setDisplayName(displayName: String): SettingsEntryBuilder { + this.displayName = displayName + return this + } + + fun setLink( + fromPage: SettingsPage? = null, + toPage: SettingsPage? = null + ): SettingsEntryBuilder { + if (fromPage != null) this.fromPage = fromPage + if (toPage != null) this.toPage = toPage + return this + } + + fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder { + this.isSearchDataDynamic = isDynamic + return this + } + + fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder { + this.hasMutableStatus = hasMutableStatus + return this + } + + fun setMacro(fn: (arguments: Bundle?) -> EntryMacro): SettingsEntryBuilder { + setStatusDataFn { fn(it).getStatusData() } + setSearchDataFn { fn(it).getSearchData() } + setUiLayoutFn { + val macro = remember { fn(it) } + macro.UiLayout() + } + return this + } + + fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder { + this.statusDataFn = fn + return this + } + + fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder { + this.searchDataFn = fn + this.isAllowSearch = true + return this + } + + fun clearSearchDataFn(): SettingsEntryBuilder { + this.searchDataFn = { null } + this.isAllowSearch = false + return this + } + + fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder { + this.sliceDataFn = fn + this.hasSliceSupport = true + return this + } + + fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder { + this.uiLayoutFn = fn + return this + } + + companion object { + fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder { + return SettingsEntryBuilder(entryName, owner) + } + + fun createLinkFrom(entryName: String, owner: SettingsPage): SettingsEntryBuilder { + return create(entryName, owner).setLink(fromPage = owner) + } + + fun createLinkTo(entryName: String, owner: SettingsPage): SettingsEntryBuilder { + return create(entryName, owner).setLink(toPage = owner) + } + + fun create(owner: SettingsPage, entryName: String, displayName: String? = null): + SettingsEntryBuilder { + return SettingsEntryBuilder(entryName, owner).setDisplayName(displayName ?: entryName) + } + + fun createInject(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder { + val name = displayName ?: "${INJECT_ENTRY_NAME}_${owner.displayName}" + return createLinkTo(INJECT_ENTRY_NAME, owner).setDisplayName(name) + } + + fun createRoot(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder { + val name = displayName ?: "${ROOT_ENTRY_NAME}_${owner.displayName}" + return createLinkTo(ROOT_ENTRY_NAME, owner).setDisplayName(name) + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt index 14dc7857dbc7..429f97bb38d0 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt @@ -30,14 +30,10 @@ data class SettingsPageWithEntry( val injectEntry: SettingsEntry, ) -private fun SettingsPage.getTitle(sppRepository: SettingsPageProviderRepository): String { - return sppRepository.getProviderOrNull(sppName)!!.getTitle(arguments) -} - /** * The repository to maintain all Settings entries */ -class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRepository) { +class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) { // Map of entry unique Id to entry private val entryMap: Map<String, SettingsEntry> @@ -66,6 +62,9 @@ class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRep if (page == null || pageWithEntryMap.containsKey(page.id)) continue val spp = sppRepository.getProviderOrNull(page.sppName) ?: continue val newEntries = spp.buildEntry(page.arguments) + // The page id could be existed already, if there are 2+ pages go to the same one. + // For now, override the previous ones, which means only the last from-page is kept. + // TODO: support multiple from-pages if necessary. pageWithEntryMap[page.id] = SettingsPageWithEntry( page = page, entries = newEntries, @@ -123,7 +122,7 @@ class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRep if (it.toPage == null) defaultTitle else { - it.toPage.getTitle(sppRepository) + it.toPage.getTitle() } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt index c810648524b9..724588f794b4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt @@ -81,6 +81,10 @@ data class SettingsPage( return getPageProvider(sppName)?.isEnabled(arguments) ?: false } + fun getTitle(): String { + return getPageProvider(sppName)?.getTitle(arguments) ?: "" + } + @Composable fun UiLayout() { getPageProvider(sppName)?.Page(arguments) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt index 32b283e9a8a9..62189dccc9bf 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -24,7 +24,12 @@ import androidx.compose.material.icons.outlined.FindInPage import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.scale +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.LayoutDirection import com.android.settingslib.spa.framework.compose.LocalNavController /** Action that navigates back to last page. */ @@ -50,6 +55,7 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) { Icon( imageVector = Icons.Outlined.ArrowBack, contentDescription = contentDescription, + modifier = Modifier.autoMirrored(), ) } } @@ -75,3 +81,10 @@ internal fun ClearAction(onClick: () -> Unit) { ) } } + +private fun Modifier.autoMirrored() = composed { + when (LocalLayoutDirection.current) { + LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f) + else -> this + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt index 730aa8f95e63..379b9a7db09b 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt @@ -113,6 +113,7 @@ class SettingsEntryRepositoryTest { @Test fun testGetEntryPath() { + SpaEnvironmentFactory.reset(spaEnvironment) assertThat( entryRepository.getEntryPathWithDisplayName( genEntryId("Layer2Entry1", SppLayer2.createSettingsPage()) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 6fb5555bb50e..c036fdb7982f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -73,6 +73,8 @@ public class InfoMediaDevice extends MediaDevice { } @VisibleForTesting + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") int getDrawableResId() { int resId; switch (mRouteInfo.getType()) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index d222b981dc91..77e514f7e037 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -443,6 +443,8 @@ public class InfoMediaManager extends MediaManager { dispatchDeviceListAdded(); } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") private void buildAllRoutes() { for (MediaRoute2Info route : mRouterManager.getAllRoutes()) { if (DEBUG) { @@ -462,6 +464,8 @@ public class InfoMediaManager extends MediaManager { return infos; } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") private synchronized void buildAvailableRoutes() { for (MediaRoute2Info route : getAvailableRoutes(mPackageName)) { if (DEBUG) { @@ -512,6 +516,8 @@ public class InfoMediaManager extends MediaManager { } } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") @VisibleForTesting void addMediaDevice(MediaRoute2Info route) { //TODO(b/258141461): Attach flag and disable reason in MediaDevice @@ -635,8 +641,8 @@ public class InfoMediaManager extends MediaManager { List<RouteListingPreference.Item> finalizedItemList = new ArrayList<>(); List<RouteListingPreference.Item> itemList = routeListingPreference.getItems(); for (RouteListingPreference.Item item : itemList) { - //Put suggested devices on the top first before further organization - if (item.getFlags() == RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE) { + // Put suggested devices on the top first before further organization + if (item.getFlags() == RouteListingPreference.Item.FLAG_SUGGESTED) { finalizedItemList.add(0, item); } else { finalizedItemList.add(item); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 50f371351f61..d24219817ad4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -31,7 +31,7 @@ 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 android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION; -import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE; +import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED; import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER; import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED; import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM; @@ -114,6 +114,8 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { setType(info); } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") private void setType(MediaRoute2Info info) { if (info == null) { mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE; @@ -335,6 +337,8 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * * @return true if the RouteInfo equals TYPE_BLE_HEADSET. */ + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") public boolean isBLEDevice() { return mRouteInfo.getType() == TYPE_BLE_HEADSET; } @@ -551,7 +555,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { private static class Api34Impl { @DoNotInline static boolean isSuggestedDevice(RouteListingPreference.Item item) { - return item != null && (item.getFlags() & FLAG_SUGGESTED_ROUTE) != 0; + return item != null && (item.getFlags() & FLAG_SUGGESTED) != 0; } @DoNotInline diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index de16d4add0ea..1c82be9c801f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -56,6 +56,8 @@ public class PhoneMediaDevice extends MediaDevice { initDeviceRecord(); } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") @Override public String getName() { CharSequence name; @@ -94,11 +96,15 @@ public class PhoneMediaDevice extends MediaDevice { return mContext.getDrawable(getDrawableResId()); } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") @VisibleForTesting int getDrawableResId() { return mDeviceIconUtil.getIconResIdFromMediaRouteType(mRouteInfo.getType()); } + // MediaRoute2Info.getType was made public on API 34, but exists since API 30. + @SuppressWarnings("NewApi") @Override public String getId() { String id; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 31038cdf184e..04c1c31dc1df 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -259,8 +259,10 @@ public class InfoMediaManagerTest { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.UPSIDE_DOWN_CAKE); final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>(); - RouteListingPreference.Item item1 = new RouteListingPreference.Item.Builder( - TEST_ID_4).setFlags(RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE).build(); + RouteListingPreference.Item item1 = + new RouteListingPreference.Item.Builder(TEST_ID_4) + .setFlags(RouteListingPreference.Item.FLAG_SUGGESTED) + .build(); RouteListingPreference.Item item2 = new RouteListingPreference.Item.Builder( TEST_ID_3).build(); preferenceItemList.add(item1); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 293a590b8f83..bf9e428552cc 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -228,6 +228,7 @@ public class SecureSettings { Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP, - Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER + Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, + Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 31eb00914410..f0bc1df2cf87 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -361,5 +361,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 3eec5653d191..e55ac1b68e99 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -106,6 +106,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS updateDoubleLineClock(); } }; + private final ContentObserver mShowWeatherObserver = new ContentObserver(null) { + @Override + public void onChange(boolean change) { + setWeatherVisibility(); + } + }; private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener mKeyguardUnlockAnimationListener = @@ -216,7 +222,15 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS UserHandle.USER_ALL ); + mSecureSettings.registerContentObserverForUser( + Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, + false, /* notifyForDescendants */ + mShowWeatherObserver, + UserHandle.USER_ALL + ); + updateDoubleLineClock(); + setWeatherVisibility(); mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( mKeyguardUnlockAnimationListener); @@ -449,6 +463,14 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } + private void setWeatherVisibility() { + if (mWeatherView != null) { + mUiExecutor.execute( + () -> mWeatherView.setVisibility( + mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE)); + } + } + /** * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of * bounds during the unlock transition. diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt index fb0c0a69e7c8..5ca36ab39dba 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt @@ -72,7 +72,7 @@ class WiredChargingRippleController @Inject constructor( height = WindowManager.LayoutParams.MATCH_PARENT layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS format = PixelFormat.TRANSLUCENT - type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY + type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG fitInsetsTypes = 0 // Ignore insets from all system bars title = "Wired Charging Animation" flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 57c99d1487f3..d02eee076bc2 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -208,9 +208,7 @@ object Flags { unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false) // TODO(b/242908637): Tracking Bug - @JvmField - val WALLPAPER_FULLSCREEN_PREVIEW = - unreleasedFlag(227, "wallpaper_fullscreen_preview", teamfood = true) + @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag(227, "wallpaper_fullscreen_preview") /** Whether the long-press gesture to open wallpaper picker is enabled. */ // TODO(b/266242192): Tracking Bug @@ -339,8 +337,7 @@ object Flags { @JvmField val UMO_TURBULENCE_NOISE = releasedFlag(909, "umo_turbulence_noise") // TODO(b/263272731): Tracking Bug - val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = - unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true) + val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple") // TODO(b/263512203): Tracking Bug val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator") @@ -507,11 +504,10 @@ object Flags { // 1300 - screenshots // TODO(b/254513155): Tracking Bug @JvmField - val SCREENSHOT_WORK_PROFILE_POLICY = - unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true) + val SCREENSHOT_WORK_PROFILE_POLICY = releasedFlag(1301, "screenshot_work_profile_policy") // TODO(b/264916608): Tracking Bug - @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata") + @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata", teamfood = true) // TODO(b/266955521): Tracking bug @JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection") @@ -532,13 +528,16 @@ object Flags { val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true) // TODO(b/266983432) Tracking Bug - val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions") + val SHARESHEET_CUSTOM_ACTIONS = + unreleasedFlag(1501, "sharesheet_custom_actions", teamfood = true) // TODO(b/266982749) Tracking Bug - val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action") + val SHARESHEET_RESELECTION_ACTION = + unreleasedFlag(1502, "sharesheet_reselection_action", teamfood = true) // TODO(b/266983474) Tracking Bug - val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview") + val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = + unreleasedFlag(1503, "sharesheet_image_text_preview", teamfood = true) // TODO(b/267355521) Tracking Bug val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW = @@ -597,12 +596,11 @@ object Flags { // 2500 - output switcher // TODO(b/261538825): Tracking Bug @JvmField - val OUTPUT_SWITCHER_ADVANCED_LAYOUT = unreleasedFlag(2500, "output_switcher_advanced_layout") + val OUTPUT_SWITCHER_ADVANCED_LAYOUT = releasedFlag(2500, "output_switcher_advanced_layout") @JvmField - val OUTPUT_SWITCHER_ROUTES_PROCESSING = - unreleasedFlag(2501, "output_switcher_routes_processing") + val OUTPUT_SWITCHER_ROUTES_PROCESSING = releasedFlag(2501, "output_switcher_routes_processing") @JvmField - val OUTPUT_SWITCHER_DEVICE_STATUS = unreleasedFlag(2502, "output_switcher_device_status") + val OUTPUT_SWITCHER_DEVICE_STATUS = releasedFlag(2502, "output_switcher_device_status") // TODO(b/20911786): Tracking Bug @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 35423f4c4140..d5d73258bb08 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -30,7 +30,6 @@ import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_RIGHT_GESTURES; -import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -85,7 +84,6 @@ import android.view.DisplayCutout; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.InsetsFrameProvider; -import android.view.InsetsState.InternalInsetsType; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -97,6 +95,7 @@ import android.view.ViewRootImpl.SurfaceChangedCallback; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; +import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; @@ -1150,12 +1149,11 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } @Override - public void showTransient(int displayId, @InternalInsetsType int[] types, - boolean isGestureOnSystemBar) { + public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) { if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_NAVIGATION_BAR)) { + if ((types & WindowInsets.Type.navigationBars()) == 0) { return; } if (!mTransientShown) { @@ -1166,11 +1164,11 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } @Override - public void abortTransient(int displayId, @InternalInsetsType int[] types) { + public void abortTransient(int displayId, @InsetsType int types) { if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_NAVIGATION_BAR)) { + if ((types & WindowInsets.Type.navigationBars()) == 0) { return; } clearTransient(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index f3712e66e330..c3d736917b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -20,8 +20,6 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; -import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -41,7 +39,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode import android.app.StatusBarManager; import android.app.StatusBarManager.WindowVisibleState; -import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -52,6 +49,7 @@ import android.os.RemoteException; import android.util.Log; import android.view.Display; import android.view.View; +import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; @@ -68,7 +66,6 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -401,11 +398,11 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override - public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) { + public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) { if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) { + if ((types & WindowInsets.Type.navigationBars()) == 0) { return; } if (!mTaskbarTransientShowing) { @@ -415,11 +412,11 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override - public void abortTransient(int displayId, int[] types) { + public void abortTransient(int displayId, @InsetsType int types) { if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) { + if ((types & WindowInsets.Type.navigationBars()) == 0) { return; } clearTransient(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java index 335172e886bb..30d2d7a8f37d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java @@ -16,7 +16,7 @@ package com.android.systemui.navigationbar.gestural; -import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE; import android.content.Context; import android.view.MotionEvent; @@ -24,14 +24,12 @@ import android.view.ViewConfiguration; public final class Utilities { - private static final int TRACKPAD_GESTURE_SCALE = 60; + private static final int TRACKPAD_GESTURE_SCALE = 200; public static boolean isTrackpadMotionEvent(boolean isTrackpadGestureBackEnabled, MotionEvent event) { - // TODO: ideally should use event.getClassification(), but currently only the move - // events get assigned the correct classification. return isTrackpadGestureBackEnabled - && (event.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN; + && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE; } public static int getTrackpadScale(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 6bfe1a099c51..be615d63a3d7 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -20,9 +20,12 @@ import android.app.KeyguardManager import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.os.UserManager import android.util.Log +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.util.kotlin.getOrNull @@ -42,11 +45,12 @@ internal class NoteTaskController @Inject constructor( private val context: Context, - private val intentResolver: NoteTaskIntentResolver, + private val resolver: NoteTaskInfoResolver, private val optionalBubbles: Optional<Bubbles>, private val optionalKeyguardManager: Optional<KeyguardManager>, private val optionalUserManager: Optional<UserManager>, @NoteTaskEnabledKey private val isEnabled: Boolean, + private val uiEventLogger: UiEventLogger, ) { /** @@ -64,7 +68,9 @@ constructor( * * That will let users open other apps in full screen, and take contextual notes. */ - fun showNoteTask(isInMultiWindowMode: Boolean = false) { + @JvmOverloads + fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) { + if (!isEnabled) return val bubbles = optionalBubbles.getOrNull() ?: return @@ -74,9 +80,12 @@ constructor( // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing. if (!userManager.isUserUnlocked) return - val intent = intentResolver.resolveIntent() ?: return + val noteTaskInfo = resolver.resolveInfo() ?: return + + uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) } // TODO(b/266686199): We should handle when app not available. For now, we log. + val intent = noteTaskInfo.toCreateNoteIntent() try { if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) { context.startActivity(intent) @@ -84,9 +93,7 @@ constructor( bubbles.showOrHideAppBubble(intent) } } catch (e: ActivityNotFoundException) { - val message = - "Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}." - Log.e(TAG, message, e) + Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e) } } @@ -114,10 +121,47 @@ constructor( ) } + /** IDs of UI events accepted by [showNoteTask]. */ + enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.") + NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294), + + /* ktlint-disable max-line-length */ + @UiEvent( + doc = + "User opened a note by pressing the stylus tail button while the screen was unlocked." + ) + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295), + @UiEvent( + doc = + "User opened a note by pressing the stylus tail button while the screen was locked." + ) + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296), + @UiEvent(doc = "User opened a note by tapping on an app shortcut.") + NOTE_OPENED_VIA_SHORTCUT(1297); + + override fun getId() = _id + } + companion object { private val TAG = NoteTaskController::class.simpleName.orEmpty() + private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent { + return Intent(ACTION_CREATE_NOTE) + .setPackage(packageName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint + // was used to start it. + .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true) + } + // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead. const val NOTE_TASK_KEY_EVENT = 311 + + // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead. + const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE" + + // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead. + const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE" } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt new file mode 100644 index 000000000000..bd822d40b950 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.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.notetask + +import android.app.role.RoleManager +import android.content.Context +import android.content.pm.PackageManager +import android.os.UserHandle +import android.util.Log +import javax.inject.Inject + +internal class NoteTaskInfoResolver +@Inject +constructor( + private val context: Context, + private val roleManager: RoleManager, + private val packageManager: PackageManager, +) { + fun resolveInfo(): NoteTaskInfo? { + // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking. + val user = context.user + val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull() + + if (packageName.isNullOrEmpty()) return null + + return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user)) + } + + /** Package name and kernel user-ID of a note-taking app. */ + data class NoteTaskInfo(val packageName: String, val uid: Int) + + companion object { + private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty() + + private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!! + + /** + * Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot + * be found. + */ + private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int { + val applicationInfo = + try { + getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Couldn't find notes app UID", e) + return 0 + } + return applicationInfo.uid + } + + // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. + const val ROLE_NOTES = "android.app.role.NOTES" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index d5f4a5a5d351..d40bf2b49975 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -16,8 +16,10 @@ package com.android.systemui.notetask +import android.app.KeyguardManager import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.kotlin.getOrNull import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import javax.inject.Inject @@ -30,6 +32,7 @@ constructor( private val noteTaskController: NoteTaskController, private val commandQueue: CommandQueue, @NoteTaskEnabledKey private val isEnabled: Boolean, + private val optionalKeyguardManager: Optional<KeyguardManager>, ) { @VisibleForTesting @@ -37,11 +40,21 @@ constructor( object : CommandQueue.Callbacks { override fun handleSystemKey(keyCode: Int) { if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) { - noteTaskController.showNoteTask() + showNoteTask() } } } + private fun showNoteTask() { + val uiEvent = + if (optionalKeyguardManager.isKeyguardLocked) { + NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED + } else { + NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON + } + noteTaskController.showNoteTask(uiEvent = uiEvent) + } + fun initialize() { if (isEnabled && optionalBubbles.isPresent) { commandQueue.addCallback(callbacks) @@ -49,3 +62,7 @@ constructor( noteTaskController.setNoteTaskShortcutEnabled(isEnabled) } } + +private val Optional<KeyguardManager>.isKeyguardLocked: Boolean + // If there's no KeyguardManager, assume that the keyguard is not locked. + get() = getOrNull()?.isKeyguardLocked ?: false diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt deleted file mode 100644 index 11dc1d7eb804..000000000000 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.systemui.notetask - -import android.app.role.RoleManager -import android.content.Context -import android.content.Intent -import javax.inject.Inject - -internal class NoteTaskIntentResolver -@Inject -constructor( - private val context: Context, - private val roleManager: RoleManager, -) { - - fun resolveIntent(): Intent? { - val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull() - - if (packageName.isNullOrEmpty()) return null - - return Intent(ACTION_CREATE_NOTE) - .setPackage(packageName) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was - // used to start it. - .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true) - } - - companion object { - // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead. - const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE" - - // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. - const val ROLE_NOTES = "android.app.role.NOTES" - - // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead. - const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt index ec6a16accc4d..b8800a242d06 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt @@ -51,7 +51,7 @@ internal interface NoteTaskModule { featureFlags: FeatureFlags, roleManager: RoleManager, ): Boolean { - val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES) + val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskInfoResolver.ROLE_NOTES) val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS) return isRoleAvailable && isFeatureEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index cfbaa48a4fa4..43869ccda2b1 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState import com.android.systemui.notetask.NoteTaskController +import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent import com.android.systemui.notetask.NoteTaskEnabledKey import javax.inject.Inject import kotlinx.coroutines.flow.flowOf @@ -64,7 +65,9 @@ constructor( } override fun onTriggered(expandable: Expandable?): OnTriggeredResult { - noteTaskController.showNoteTask() + noteTaskController.showNoteTask( + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE + ) return OnTriggeredResult.Handled } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt index f203e7a51643..3ac5bfa09aaa 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt @@ -21,7 +21,7 @@ import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import com.android.systemui.notetask.NoteTaskController -import com.android.systemui.notetask.NoteTaskIntentResolver +import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent import javax.inject.Inject /** Activity responsible for launching the note experience, and finish. */ @@ -34,7 +34,10 @@ constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - noteTaskController.showNoteTask(isInMultiWindowMode) + noteTaskController.showNoteTask( + isInMultiWindowMode = isInMultiWindowMode, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, + ) finish() } @@ -46,7 +49,7 @@ constructor( return Intent(context, LaunchNoteTaskActivity::class.java).apply { // Intent's action must be set in shortcuts, or an exception will be thrown. // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead. - action = NoteTaskIntentResolver.ACTION_CREATE_NOTE + action = NoteTaskController.ACTION_CREATE_NOTE } } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index c573080f8f28..f53f8243131c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -685,6 +685,7 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mInstantExpanding; private boolean mAnimateAfterExpanding; private boolean mIsFlinging; + private boolean mLastFlingWasExpanding; private String mViewName; private float mInitialExpandY; private float mInitialExpandX; @@ -2142,6 +2143,7 @@ public final class NotificationPanelViewController implements Dumpable { @VisibleForTesting void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { + mLastFlingWasExpanding = expand; mHeadsUpTouchHelper.notifyFling(!expand); mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */); setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f); @@ -2531,7 +2533,7 @@ public final class NotificationPanelViewController implements Dumpable { } // defer touches on QQS to shade while shade is collapsing. Added margin for error // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS. - if (!mSplitShadeEnabled + if (!mSplitShadeEnabled && !mLastFlingWasExpanding && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) { mShadeLog.logMotionEvent(event, "handleQsTouch: shade touched while collapsing, QS tracking disabled"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 3aaad87b8eab..2cf1f53b4499 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -53,7 +53,6 @@ import android.os.Process; import android.os.RemoteException; import android.util.Pair; import android.util.SparseArray; -import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; @@ -371,22 +370,22 @@ public class CommandQueue extends IStatusBar.Stub implements String packageName, LetterboxDetails[] letterboxDetails) { } /** - * @see IStatusBar#showTransient(int, int[], boolean). + * @see IStatusBar#showTransient(int, int, boolean). */ - default void showTransient(int displayId, @InternalInsetsType int[] types) { } + default void showTransient(int displayId, @InsetsType int types) { } /** - * @see IStatusBar#showTransient(int, int[], boolean). + * @see IStatusBar#showTransient(int, int, boolean). */ - default void showTransient(int displayId, @InternalInsetsType int[] types, + default void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) { showTransient(displayId, types); } /** - * @see IStatusBar#abortTransient(int, int[]). + * @see IStatusBar#abortTransient(int, int). */ - default void abortTransient(int displayId, @InternalInsetsType int[] types) { } + default void abortTransient(int displayId, @InsetsType int types) { } /** * Called to notify System UI that a warning about the device going to sleep @@ -1131,17 +1130,23 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) { + public void showTransient(int displayId, int types, boolean isGestureOnSystemBar) { synchronized (mLock) { - mHandler.obtainMessage(MSG_SHOW_TRANSIENT, displayId, isGestureOnSystemBar ? 1 : 0, - types).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = displayId; + args.argi2 = types; + args.argi3 = isGestureOnSystemBar ? 1 : 0; + mHandler.obtainMessage(MSG_SHOW_TRANSIENT, args).sendToTarget(); } } @Override - public void abortTransient(int displayId, int[] types) { + public void abortTransient(int displayId, int types) { synchronized (mLock) { - mHandler.obtainMessage(MSG_ABORT_TRANSIENT, displayId, 0, types).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = displayId; + args.argi2 = types; + mHandler.obtainMessage(MSG_ABORT_TRANSIENT, args).sendToTarget(); } } @@ -1644,17 +1649,21 @@ public class CommandQueue extends IStatusBar.Stub implements args.recycle(); break; case MSG_SHOW_TRANSIENT: { - final int displayId = msg.arg1; - final int[] types = (int[]) msg.obj; - final boolean isGestureOnSystemBar = msg.arg2 != 0; + args = (SomeArgs) msg.obj; + final int displayId = args.argi1; + final int types = args.argi2; + final boolean isGestureOnSystemBar = args.argi3 != 0; + args.recycle(); for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).showTransient(displayId, types, isGestureOnSystemBar); } break; } case MSG_ABORT_TRANSIENT: { - final int displayId = msg.arg1; - final int[] types = (int[]) msg.obj; + args = (SomeArgs) msg.obj; + final int displayId = args.argi1; + final int types = args.argi2; + args.recycle(); for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).abortTransient(displayId, types); } 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 fe76c7dca26a..81c71974b699 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -31,6 +31,7 @@ import android.os.Handler import android.os.UserHandle import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS +import android.provider.Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED import android.util.Log import android.view.ContextThemeWrapper import android.view.View @@ -245,6 +246,17 @@ constructor( datePlugin != null && weatherPlugin != null } + fun isWeatherEnabled(): Boolean { + execution.assertIsMainThread() + val defaultValue = context.getResources().getBoolean( + com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault) + val showWeather = secureSettings.getIntForUser( + LOCK_SCREEN_WEATHER_ENABLED, + if (defaultValue) 1 else 0, + userTracker.userId) == 1 + return showWeather + } + private fun updateBypassEnabled() { val bypassEnabled = bypassController.bypassEnabled smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 856d7dedb4eb..fecaa3a0caf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.phone; -import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static android.view.InsetsState.containsType; - import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; @@ -36,8 +33,8 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; import android.util.Slog; -import android.view.InsetsState.InternalInsetsType; import android.view.KeyEvent; +import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; @@ -168,11 +165,11 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba } @Override - public void abortTransient(int displayId, @InternalInsetsType int[] types) { + public void abortTransient(int displayId, @InsetsType int types) { if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_STATUS_BAR)) { + if ((types & WindowInsets.Type.statusBars()) == 0) { return; } mCentralSurfaces.clearTransient(); @@ -489,12 +486,11 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba } @Override - public void showTransient(int displayId, @InternalInsetsType int[] types, - boolean isGestureOnSystemBar) { + public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) { if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_STATUS_BAR)) { + if ((types & WindowInsets.Type.statusBars()) == 0) { return; } mCentralSurfaces.showTransientUnchecked(); 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 e595ddf46a89..1966a6657acb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -21,8 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; -import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; @@ -100,6 +98,7 @@ import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; +import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -943,7 +942,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up the initial notification state. This needs to happen before CommandQueue.disable() setUpPresenter(); - if (containsType(result.mTransientBarTypes, ITYPE_STATUS_BAR)) { + if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) { showTransientUnchecked(); } mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance, 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 e5ab47325229..5cf01af92dcd 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 @@ -76,6 +76,9 @@ interface UserRepository { /** Whether user switching is currently in progress. */ val userSwitchingInProgress: Flow<Boolean> + /** User ID of the main user. */ + val mainUserId: Int + /** User ID of the last non-guest selected user. */ val lastSelectedNonGuestUserId: Int @@ -130,7 +133,9 @@ constructor( private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() - override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM + override var mainUserId: Int = UserHandle.USER_NULL + private set + override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL private set override val isGuestUserAutoCreated: Boolean = @@ -172,6 +177,11 @@ constructor( // The guest user is always last, regardless of creation time. .sortedBy { it.isGuest } } + + if (mainUserId == UserHandle.USER_NULL) { + val mainUser = withContext(backgroundDispatcher) { manager.mainUser } + mainUser?.let { mainUserId = it.identifier } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt index a3748852a616..0a07439e1876 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt @@ -139,11 +139,11 @@ constructor( } applicationScope.launch { - var newUserId = UserHandle.USER_SYSTEM + var newUserId = repository.mainUserId if (targetUserId == UserHandle.USER_NULL) { // When a target user is not specified switch to last non guest user: val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId - if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) { + if (lastSelectedNonGuestUserHandle != repository.mainUserId) { val info = withContext(backgroundDispatcher) { manager.getUserInfo(lastSelectedNonGuestUserHandle) @@ -215,8 +215,11 @@ constructor( // Create a new guest in the foreground, and then immediately switch to it val newGuestId = create(showDialog, dismissDialog) if (newGuestId == UserHandle.USER_NULL) { - Log.e(TAG, "Could not create new guest, switching back to system user") - switchUser(UserHandle.USER_SYSTEM) + Log.e(TAG, "Could not create new guest, switching back to main user") + val mainUser = withContext(backgroundDispatcher) { manager.mainUser?.identifier } + + mainUser?.let { switchUser(it) } + withContext(backgroundDispatcher) { manager.removeUserWhenPossible( UserHandle.of(currentUser.id), diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 36b3f897190d..ccc4e4af4ac8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -300,8 +300,9 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { ArgumentCaptor<ContentObserver> observerCaptor = ArgumentCaptor.forClass(ContentObserver.class); mController.init(); - verify(mSecureSettings).registerContentObserverForUser(any(String.class), - anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL)); + verify(mSecureSettings).registerContentObserverForUser( + eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK), + anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL)); ContentObserver observer = observerCaptor.getValue(); mExecutor.runAllReady(); @@ -347,6 +348,22 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { assertEquals(0, mController.getClockBottom(10)); } + @Test + public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() { + when(mSmartspaceController.isWeatherEnabled()).thenReturn(true); + ArgumentCaptor<ContentObserver> observerCaptor = + ArgumentCaptor.forClass(ContentObserver.class); + mController.init(); + verify(mSecureSettings).registerContentObserverForUser( + eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(), + observerCaptor.capture(), eq(UserHandle.USER_ALL)); + ContentObserver observer = observerCaptor.getValue(); + mExecutor.runAllReady(); + // When a settings change has occurred, check that view is visible. + observer.onChange(true); + mExecutor.runAllReady(); + assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility()); + } private void verifyAttachment(VerificationMode times) { verify(mClockRegistry, times).registerClockChangeListener( diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java index 509d5f0cb420..70ba306bfe46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java @@ -16,10 +16,10 @@ package com.android.systemui.navigationbar.gestural; -import static android.view.InputDevice.SOURCE_TOUCHPAD; -import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.view.InputDevice.SOURCE_MOUSE; import static android.view.MotionEvent.AXIS_GESTURE_X_OFFSET; import static android.view.MotionEvent.AXIS_GESTURE_Y_OFFSET; +import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE; import static com.google.common.truth.Truth.assertThat; @@ -57,14 +57,9 @@ public class MotionEventsHandlerTest extends SysuiTestCase { @Test public void onTouchEvent_touchScreen_hasCorrectDisplacements() { MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 100, 100, 0); - // TODO: change to use classification after gesture library is ported. - down.setSource(SOURCE_TOUCHSCREEN); MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 150, 125, 0); - move1.setSource(SOURCE_TOUCHSCREEN); MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 200, 150, 0); - move2.setSource(SOURCE_TOUCHSCREEN); MotionEvent up = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 250, 175, 0); - up.setSource(SOURCE_TOUCHSCREEN); mMotionEventsHandler.onMotionEvent(down); mMotionEventsHandler.onMotionEvent(move1); @@ -90,8 +85,8 @@ public class MotionEventsHandlerTest extends SysuiTestCase { downPointerProperties[0].id = 1; downPointerProperties[0].toolType = MotionEvent.TOOL_TYPE_FINGER; MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1, - downPointerProperties, downPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, - SOURCE_TOUCHPAD, 0); + downPointerProperties, downPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE, + 0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE); MotionEvent.PointerCoords[] movePointerCoords1 = new MotionEvent.PointerCoords[1]; movePointerCoords1[0] = new MotionEvent.PointerCoords(); @@ -103,8 +98,8 @@ public class MotionEventsHandlerTest extends SysuiTestCase { movePointerProperties1[0].id = 1; movePointerProperties1[0].toolType = MotionEvent.TOOL_TYPE_FINGER; MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 1, - movePointerProperties1, movePointerCoords1, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD, - 0); + movePointerProperties1, movePointerCoords1, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE, + 0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE); MotionEvent.PointerCoords[] movePointerCoords2 = new MotionEvent.PointerCoords[1]; movePointerCoords2[0] = new MotionEvent.PointerCoords(); @@ -116,8 +111,8 @@ public class MotionEventsHandlerTest extends SysuiTestCase { movePointerProperties2[0].id = 1; movePointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER; MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 1, - movePointerProperties2, movePointerCoords2, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD, - 0); + movePointerProperties2, movePointerCoords2, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE, + 0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE); MotionEvent.PointerCoords[] upPointerCoords = new MotionEvent.PointerCoords[1]; upPointerCoords[0] = new MotionEvent.PointerCoords(); @@ -129,7 +124,8 @@ public class MotionEventsHandlerTest extends SysuiTestCase { upPointerProperties2[0].id = 1; upPointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER; MotionEvent up = MotionEvent.obtain(0, 2, MotionEvent.ACTION_UP, 1, - upPointerProperties2, upPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_TOUCHPAD, 0); + upPointerProperties2, upPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE, + 0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE); mMotionEventsHandler.onMotionEvent(down); mMotionEventsHandler.onMotionEvent(move1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 8440455127bd..39c4e06ff0bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -23,10 +23,14 @@ import android.content.pm.PackageManager import android.os.UserManager import android.test.suitebuilder.annotation.SmallTest import androidx.test.runner.AndroidJUnit4 +import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase -import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE +import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE +import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent +import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.wm.shell.bubbles.Bubbles @@ -36,8 +40,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations /** @@ -50,24 +54,23 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) internal class NoteTaskControllerTest : SysuiTestCase() { - private val notesIntent = Intent(ACTION_CREATE_NOTE) - @Mock lateinit var context: Context @Mock lateinit var packageManager: PackageManager - @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver + @Mock lateinit var resolver: NoteTaskInfoResolver @Mock lateinit var bubbles: Bubbles @Mock lateinit var optionalBubbles: Optional<Bubbles> @Mock lateinit var keyguardManager: KeyguardManager @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager> @Mock lateinit var optionalUserManager: Optional<UserManager> @Mock lateinit var userManager: UserManager + @Mock lateinit var uiEventLogger: UiEventLogger @Before fun setUp() { MockitoAnnotations.initMocks(this) whenever(context.packageManager).thenReturn(packageManager) - whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent) + whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID)) whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager) whenever(optionalUserManager.orElse(null)).thenReturn(userManager) @@ -77,101 +80,182 @@ internal class NoteTaskControllerTest : SysuiTestCase() { private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController { return NoteTaskController( context = context, - intentResolver = noteTaskIntentResolver, + resolver = resolver, optionalBubbles = optionalBubbles, optionalKeyguardManager = optionalKeyguardManager, optionalUserManager = optionalUserManager, isEnabled = isEnabled, + uiEventLogger = uiEventLogger, ) } // region showNoteTask @Test - fun showNoteTask_keyguardIsLocked_shouldStartActivity() { + fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() { whenever(keyguardManager.isKeyguardLocked).thenReturn(true) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE, + ) - verify(context).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + val intentCaptor = argumentCaptor<Intent>() + verify(context).startActivity(capture(intentCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) + assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) + assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() + } + verifyZeroInteractions(bubbles) + verify(uiEventLogger) + .log( + ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE, + NOTES_UID, + NOTES_PACKAGE_NAME + ) } @Test - fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() { + fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() { whenever(keyguardManager.isKeyguardLocked).thenReturn(false) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + ) + + verifyZeroInteractions(context) + val intentCaptor = argumentCaptor<Intent>() + verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) + assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) + assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() + } + verify(uiEventLogger) + .log( + ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + NOTES_UID, + NOTES_PACKAGE_NAME + ) + } + + @Test + fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() { + whenever(keyguardManager.isKeyguardLocked).thenReturn(false) - verify(bubbles).showOrHideAppBubble(notesIntent) - verify(context, never()).startActivity(notesIntent) + createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null) + + verifyZeroInteractions(context) + val intentCaptor = argumentCaptor<Intent>() + verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) + assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) + assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() + } + verifyZeroInteractions(uiEventLogger) } @Test - fun showNoteTask_isInMultiWindowMode_shouldStartActivity() { + fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() { whenever(keyguardManager.isKeyguardLocked).thenReturn(false) - createNoteTaskController().showNoteTask(isInMultiWindowMode = true) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = true, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, + ) - verify(context).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + val intentCaptor = argumentCaptor<Intent>() + verify(context).startActivity(capture(intentCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) + assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) + assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() + } + verifyZeroInteractions(bubbles) + verify(uiEventLogger) + .log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME) } @Test fun showNoteTask_bubblesIsNull_shouldDoNothing() { whenever(optionalBubbles.orElse(null)).thenReturn(null) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON + ) - verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + verifyZeroInteractions(context, bubbles, uiEventLogger) } @Test fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() { whenever(optionalKeyguardManager.orElse(null)).thenReturn(null) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + ) - verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + verifyZeroInteractions(context, bubbles, uiEventLogger) } @Test fun showNoteTask_userManagerIsNull_shouldDoNothing() { whenever(optionalUserManager.orElse(null)).thenReturn(null) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + ) - verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + verifyZeroInteractions(context, bubbles, uiEventLogger) } @Test fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() { - whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null) + whenever(resolver.resolveInfo()).thenReturn(null) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + ) - verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + verifyZeroInteractions(context, bubbles, uiEventLogger) } @Test fun showNoteTask_flagDisabled_shouldDoNothing() { - createNoteTaskController(isEnabled = false).showNoteTask() + createNoteTaskController(isEnabled = false) + .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) - verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + verifyZeroInteractions(context, bubbles, uiEventLogger) } @Test fun showNoteTask_userIsLocked_shouldDoNothing() { whenever(userManager.isUserUnlocked).thenReturn(false) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false) + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + ) - verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showOrHideAppBubble(notesIntent) + verifyZeroInteractions(context, bubbles, uiEventLogger) } // endregion @@ -206,4 +290,9 @@ internal class NoteTaskControllerTest : SysuiTestCase() { assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString()) } // endregion + + private companion object { + const val NOTES_PACKAGE_NAME = "com.android.note.app" + const val NOTES_UID = 123456 + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt new file mode 100644 index 000000000000..d6495d8fe1b7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.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.notetask + +import android.app.role.RoleManager +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.test.suitebuilder.annotation.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.eq +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.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.MockitoAnnotations + +/** + * Tests for [NoteTaskInfoResolver]. + * + * Build/Install/Run: + * - atest SystemUITests:NoteTaskInfoResolverTest + */ +@SmallTest +@RunWith(AndroidJUnit4::class) +internal class NoteTaskInfoResolverTest : SysuiTestCase() { + + @Mock lateinit var packageManager: PackageManager + @Mock lateinit var roleManager: RoleManager + + private lateinit var underTest: NoteTaskInfoResolver + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = NoteTaskInfoResolver(context, roleManager, packageManager) + } + + @Test + fun resolveInfo_shouldReturnInfo() { + val packageName = "com.android.note.app" + val uid = 123456 + whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user)) + .then { listOf(packageName) } + whenever( + packageManager.getApplicationInfoAsUser( + eq(packageName), + any<PackageManager.ApplicationInfoFlags>(), + eq(context.user) + ) + ) + .thenReturn(ApplicationInfo().apply { this.uid = uid }) + + val actual = underTest.resolveInfo() + + requireNotNull(actual) { "Note task info must not be null" } + assertThat(actual.packageName).isEqualTo(packageName) + assertThat(actual.uid).isEqualTo(uid) + } + + @Test + fun resolveInfo_packageManagerThrowsException_shouldReturnInfoWithZeroUid() { + val packageName = "com.android.note.app" + whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user)) + .then { listOf(packageName) } + whenever( + packageManager.getApplicationInfoAsUser( + eq(packageName), + any<PackageManager.ApplicationInfoFlags>(), + eq(context.user) + ) + ) + .thenThrow(PackageManager.NameNotFoundException(packageName)) + + val actual = underTest.resolveInfo() + + requireNotNull(actual) { "Note task info must not be null" } + assertThat(actual.packageName).isEqualTo(packageName) + assertThat(actual.uid).isEqualTo(0) + } + + @Test + fun resolveInfo_noRoleHolderIsSet_shouldReturnNull() { + whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskInfoResolver.ROLE_NOTES), any())) + .then { listOf<String>() } + + val actual = underTest.resolveInfo() + + assertThat(actual).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 010ac5bbb2d9..53720ffdff94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -15,11 +15,14 @@ */ package com.android.systemui.notetask +import android.app.KeyguardManager import android.test.suitebuilder.annotation.SmallTest import android.view.KeyEvent import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.wm.shell.bubbles.Bubbles import java.util.Optional @@ -30,6 +33,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations /** @@ -55,12 +59,16 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) } - private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer { + private fun createNoteTaskInitializer( + isEnabled: Boolean = true, + optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(), + ): NoteTaskInitializer { return NoteTaskInitializer( optionalBubbles = optionalBubbles, noteTaskController = noteTaskController, commandQueue = commandQueue, isEnabled = isEnabled, + optionalKeyguardManager = optionalKeyguardManager, ) } @@ -105,19 +113,44 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { // region handleSystemKey @Test - fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() { - createNoteTaskInitializer() + fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() { + val keyguardManager = + mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) } + createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager)) .callbacks .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) - verify(noteTaskController).showNoteTask() + verify(noteTaskController) + .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) + } + + @Test + fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() { + val keyguardManager = + mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) } + createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager)) + .callbacks + .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) + + verify(noteTaskController) + .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED) + } + + @Test + fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() { + createNoteTaskInitializer(optionalKeyguardManager = Optional.empty()) + .callbacks + .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) + + verify(noteTaskController) + .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) } @Test fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() { createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN) - verify(noteTaskController, never()).showNoteTask() + verifyZeroInteractions(noteTaskController) } // endregion } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt deleted file mode 100644 index 18be92ba27cf..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.systemui.notetask - -import android.app.role.RoleManager -import android.content.Intent -import android.content.pm.PackageManager -import android.test.suitebuilder.annotation.SmallTest -import androidx.test.runner.AndroidJUnit4 -import com.android.systemui.SysuiTestCase -import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE -import com.android.systemui.util.mockito.eq -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.mockito.Mock -import org.mockito.Mockito.any -import org.mockito.MockitoAnnotations - -/** - * Tests for [NoteTaskIntentResolver]. - * - * Build/Install/Run: - * - atest SystemUITests:NoteTaskIntentResolverTest - */ -@SmallTest -@RunWith(AndroidJUnit4::class) -internal class NoteTaskIntentResolverTest : SysuiTestCase() { - - @Mock lateinit var packageManager: PackageManager - @Mock lateinit var roleManager: RoleManager - - private lateinit var underTest: NoteTaskIntentResolver - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - underTest = NoteTaskIntentResolver(context, roleManager) - } - - @Test - fun resolveIntent_shouldReturnIntentInStylusMode() { - val packageName = "com.android.note.app" - whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user)) - .then { listOf(packageName) } - - val actual = underTest.resolveIntent() - - requireNotNull(actual) { "Intent must not be null" } - assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE) - assertThat(actual.`package`).isEqualTo(packageName) - val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE) - assertThat(expectedExtra).isEqualTo(true) - val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK - assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) - } - - @Test - fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() { - whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any())) - .then { listOf<String>() } - - val actual = underTest.resolveIntent() - - assertThat(actual).isNull() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index a1d42a0ce505..cdc683f8f8f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -27,7 +27,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState import com.android.systemui.notetask.NoteTaskController -import com.android.systemui.util.mockito.whenever +import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -53,7 +53,6 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(noteTaskController.showNoteTask()).then {} } private fun createUnderTest(isEnabled: Boolean) = @@ -96,6 +95,7 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { underTest.onTriggered(expandable = null) - verify(noteTaskController).showNoteTask() + verify(noteTaskController) + .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index b1ca1c02f6da..f581154f66c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -18,8 +18,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; -import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; import static org.mockito.ArgumentMatchers.anyInt; @@ -158,7 +156,7 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testShowTransient() { - int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}; + int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars(); mCommandQueue.showTransient(DEFAULT_DISPLAY, types, true /* isGestureOnSystemBar */); waitForIdleSync(); verify(mCallbacks).showTransient(eq(DEFAULT_DISPLAY), eq(types), eq(true)); @@ -166,7 +164,7 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testShowTransientForSecondaryDisplay() { - int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}; + int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars(); mCommandQueue.showTransient(SECONDARY_DISPLAY, types, true /* isGestureOnSystemBar */); waitForIdleSync(); verify(mCallbacks).showTransient(eq(SECONDARY_DISPLAY), eq(types), eq(true)); @@ -174,7 +172,7 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testAbortTransient() { - int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}; + int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars(); mCommandQueue.abortTransient(DEFAULT_DISPLAY, types); waitForIdleSync(); verify(mCallbacks).abortTransient(eq(DEFAULT_DISPLAY), eq(types)); @@ -182,7 +180,7 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testAbortTransientForSecondaryDisplay() { - int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}; + int types = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars(); mCommandQueue.abortTransient(SECONDARY_DISPLAY, types); waitForIdleSync(); verify(mCallbacks).abortTransient(eq(SECONDARY_DISPLAY), eq(types)); 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 ccf378a71abd..9312643a1453 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 @@ -128,6 +128,11 @@ class UserRepositoryImplTest : SysuiTestCase() { @Test fun refreshUsers() = runSelfCancelingTest { + val mainUserId = 10 + val mainUser = mock(UserHandle::class.java) + whenever(manager.mainUser).thenReturn(mainUser) + whenever(mainUser.identifier).thenReturn(mainUserId) + underTest = create(this) val initialExpectedValue = setUpUsers( @@ -166,6 +171,7 @@ class UserRepositoryImplTest : SysuiTestCase() { assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1]) assertThat(selectedUserInfo?.isGuest).isTrue() assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId) + assertThat(underTest.mainUserId).isEqualTo(mainUserId) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt index fb781e850b09..cc23485f4a61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt @@ -204,10 +204,12 @@ class GuestUserInteractorTest : SysuiTestCase() { } @Test - fun `exit - last non-guest was removed - returns to system`() = + fun `exit - last non-guest was removed - returns to main user`() = runBlocking(IMMEDIATE) { val removedUserId = 310 + val mainUserId = 10 repository.lastSelectedNonGuestUserId = removedUserId + repository.mainUserId = mainUserId repository.setSelectedUserInfo(GUEST_USER_INFO) underTest.exit( @@ -221,7 +223,7 @@ class GuestUserInteractorTest : SysuiTestCase() { verify(manager, never()).markGuestForDeletion(anyInt()) verify(manager, never()).removeUser(anyInt()) - verify(switchUser).invoke(UserHandle.USER_SYSTEM) + verify(switchUser).invoke(mainUserId) } @Test 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 1a8e244bf54f..53bb340fc167 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 @@ -28,6 +28,10 @@ import kotlinx.coroutines.flow.filterNotNull 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 _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel()) override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> = @@ -43,7 +47,8 @@ class FakeUserRepository : UserRepository { override val userSwitchingInProgress: Flow<Boolean> get() = _userSwitchingInProgress - override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM + override var mainUserId: Int = MAIN_USER_ID + override var lastSelectedNonGuestUserId: Int = mainUserId private var _isGuestUserAutoCreated: Boolean = false override val isGuestUserAutoCreated: Boolean diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index cc8aec7f3261..a4fd52de3372 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -39,6 +39,10 @@ } ], "file_patterns": ["BinaryTransparencyService\\.java"] + }, + { + "name": "BinaryTransparencyHostTest", + "file_patterns": ["BinaryTransparencyService\\.java"] } ], "presubmit-large": [ @@ -69,11 +73,5 @@ ], "file_patterns": ["ClipboardService\\.java"] } - ], - "postsubmit": [ - { - "name": "BinaryTransparencyHostTest", - "file_patterns": ["BinaryTransparencyService\\.java"] - } ] } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 04c0d64e42a1..2b2de81e36c6 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3888,7 +3888,7 @@ public final class ProcessList { return runList; } - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) int getLruSizeLOSP() { return mLruProcesses.size(); } @@ -3896,7 +3896,7 @@ public final class ProcessList { /** * Return the reference to the LRU list, call this function for read-only access */ - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) ArrayList<ProcessRecord> getLruProcessesLOSP() { return mLruProcesses; } @@ -3904,7 +3904,7 @@ public final class ProcessList { /** * Return the reference to the LRU list, call this function for read/write access */ - @GuardedBy({"mService", "mProfileLock"}) + @GuardedBy({"mService", "mProcLock"}) ArrayList<ProcessRecord> getLruProcessesLSP() { return mLruProcesses; } @@ -3913,12 +3913,12 @@ public final class ProcessList { * For test only */ @VisibleForTesting - @GuardedBy({"mService", "mProfileLock"}) + @GuardedBy({"mService", "mProcLock"}) void setLruProcessServiceStartLSP(int pos) { mLruProcessServiceStart = pos; } - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) int getLruProcessServiceStartLOSP() { return mLruProcessServiceStart; } @@ -3931,7 +3931,7 @@ public final class ProcessList { * to most recent used ProcessRecord. * @param callback The callback interface to accept the current ProcessRecord. */ - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) void forEachLruProcessesLOSP(boolean iterateForward, @NonNull Consumer<ProcessRecord> callback) { if (iterateForward) { @@ -3956,7 +3956,7 @@ public final class ProcessList { * a non-null object, the search will be halted and this object will be used * as the return value of this search function. */ - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) <R> R searchEachLruProcessesLOSP(boolean iterateForward, @NonNull Function<ProcessRecord, R> callback) { if (iterateForward) { @@ -3977,17 +3977,17 @@ public final class ProcessList { return null; } - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) boolean isInLruListLOSP(ProcessRecord app) { return mLruProcesses.contains(app); } - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) int getLruSeqLOSP() { return mLruSeq; } - @GuardedBy(anyOf = {"mService", "mProfileLock"}) + @GuardedBy(anyOf = {"mService", "mProcLock"}) MyProcessMap getProcessNamesLOSP() { return mProcessNames; } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 12784bf7e970..60a7f9371837 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -104,6 +104,7 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT, DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, + DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT, DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE, DeviceConfig.NAMESPACE_HDMI_CONTROL }; diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java index 969a174f49c7..0b5c1c171354 100644 --- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java +++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java @@ -150,6 +150,13 @@ public final class SensorOverlays { } /** + * Returns if the sensor is side fps. + */ + public boolean isSfps() { + return mSidefpsController.isPresent(); + } + + /** * Consumer for a biometric overlay controller. * * This behaves like a normal {@link Consumer} except that it will trap and log diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index a90679e755cf..932c0b4948a0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -236,8 +236,14 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @Override public void onError(int errorCode, int vendorCode) { - super.onError(errorCode, vendorCode); - + if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR + && vendorCode == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED + && mSensorOverlays.isSfps()) { + super.onError(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED, + 0 /* vendorCode */); + } else { + super.onError(errorCode, vendorCode); + } if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) { BiometricNotificationUtils.showBadCalibrationNotification(getContext()); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 513b3e3e6e86..cf54662dfa7d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -146,7 +146,14 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps } }); mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); - super.onAcquired(acquiredInfo, vendorCode); + if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR + && vendorCode == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED + && mSensorOverlays.isSfps()) { + super.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED, + 0 /* vendorCode */); + } else { + super.onAcquired(acquiredInfo, vendorCode); + } } @Override @@ -274,8 +281,5 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps } @Override - public void onPowerPressed() { - onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED, - 0 /* vendorCode */); - } + public void onPowerPressed() { } } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 29caefbfd1d9..91ef167dfc3a 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -733,7 +733,7 @@ public class DisplayModeDirector { /** * Sets the display mode switching type. - * @param newType + * @param newType new mode switching type */ public void setModeSwitchingType(@DisplayManager.SwitchingType int newType) { synchronized (mLock) { @@ -850,6 +850,18 @@ public class DisplayModeDirector { notifyDesiredDisplayModeSpecsChangedLocked(); } + @GuardedBy("mLock") + private float getMaxRefreshRateLocked(int displayId) { + Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); + float maxRefreshRate = 0f; + for (Display.Mode mode : modes) { + if (mode.getRefreshRate() > maxRefreshRate) { + maxRefreshRate = mode.getRefreshRate(); + } + } + return maxRefreshRate; + } + private void notifyDesiredDisplayModeSpecsChangedLocked() { if (mDesiredDisplayModeSpecsListener != null && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) { @@ -1186,29 +1198,33 @@ public class DisplayModeDirector { // rest of low priority voters. It votes [0, max(PEAK, MIN)] public static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7; + // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh + // rate to max value (same as for PRIORITY_UDFPS) on lock screen + public static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8; + // For concurrent displays we want to limit refresh rate on all displays - public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 8; + public static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9; // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - public static final int PRIORITY_LOW_POWER_MODE = 9; + public static final int PRIORITY_LOW_POWER_MODE = 10; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 10; + public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - public static final int PRIORITY_SKIN_TEMPERATURE = 11; + public static final int PRIORITY_SKIN_TEMPERATURE = 12; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - public static final int PRIORITY_PROXIMITY = 12; + public static final int PRIORITY_PROXIMITY = 13; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - public static final int PRIORITY_UDFPS = 13; + public static final int PRIORITY_UDFPS = 14; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. @@ -1325,6 +1341,8 @@ public class DisplayModeDirector { return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE"; case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE: return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE"; + case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE: + return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE"; default: return Integer.toString(priority); } @@ -2564,6 +2582,7 @@ public class DisplayModeDirector { private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub { private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray(); + private final SparseBooleanArray mAuthenticationPossible = new SparseBooleanArray(); public void observe() { StatusBarManagerInternal statusBar = @@ -2576,38 +2595,38 @@ public class DisplayModeDirector { @Override public void onRequestEnabled(int displayId) { synchronized (mLock) { - updateRefreshRateStateLocked(displayId, true /*enabled*/); + mUdfpsRefreshRateEnabled.put(displayId, true); + updateVoteLocked(displayId, true, Vote.PRIORITY_UDFPS); } } @Override public void onRequestDisabled(int displayId) { synchronized (mLock) { - updateRefreshRateStateLocked(displayId, false /*enabled*/); + mUdfpsRefreshRateEnabled.put(displayId, false); + updateVoteLocked(displayId, false, Vote.PRIORITY_UDFPS); } } - private void updateRefreshRateStateLocked(int displayId, boolean enabled) { - mUdfpsRefreshRateEnabled.put(displayId, enabled); - updateVoteLocked(displayId); + @Override + public void onAuthenticationPossible(int displayId, boolean isPossible) { + synchronized (mLock) { + mAuthenticationPossible.put(displayId, isPossible); + updateVoteLocked(displayId, isPossible, + Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE); + } } - private void updateVoteLocked(int displayId) { + @GuardedBy("mLock") + private void updateVoteLocked(int displayId, boolean enabled, int votePriority) { final Vote vote; - if (mUdfpsRefreshRateEnabled.get(displayId)) { - Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); - float maxRefreshRate = 0f; - for (Display.Mode mode : modes) { - if (mode.getRefreshRate() > maxRefreshRate) { - maxRefreshRate = mode.getRefreshRate(); - } - } + if (enabled) { + float maxRefreshRate = DisplayModeDirector.this.getMaxRefreshRateLocked(displayId); vote = Vote.forPhysicalRefreshRates(maxRefreshRate, maxRefreshRate); } else { vote = null; } - - DisplayModeDirector.this.updateVoteLocked(displayId, Vote.PRIORITY_UDFPS, vote); + DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote); } void dumpLocked(PrintWriter pw) { @@ -2618,6 +2637,13 @@ public class DisplayModeDirector { final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled"; pw.println(" Display " + displayId + ": " + enabled); } + pw.println(" mAuthenticationPossible: "); + for (int i = 0; i < mAuthenticationPossible.size(); i++) { + final int displayId = mAuthenticationPossible.keyAt(i); + final String isPossible = mAuthenticationPossible.valueAt(i) ? "possible" + : "impossible"; + pw.println(" Display " + displayId + ": " + isPossible); + } } } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 60903864a881..1086c5565227 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -161,7 +161,11 @@ final class LogicalDisplay { // Indicates the position of the display, POSITION_UNKNOWN could mean it hasn't been specified, // or this is a virtual display etc. - private int mPosition = Layout.Display.POSITION_UNKNOWN; + private int mDevicePosition = Layout.Display.POSITION_UNKNOWN; + + // Indicates that something other than the primary display device info has changed and needs to + // be handled in the next update. + private boolean mDirty = false; /** * The ID of the brightness throttling data that should be used. This can change e.g. in @@ -181,11 +185,14 @@ final class LogicalDisplay { mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID; } - public void setPositionLocked(int position) { - mPosition = position; + public void setDevicePositionLocked(int position) { + if (mDevicePosition != position) { + mDevicePosition = position; + mDirty = true; + } } - public int getPositionLocked() { - return mPosition; + public int getDevicePositionLocked() { + return mDevicePosition; } /** @@ -343,9 +350,11 @@ final class LogicalDisplay { // logical display that they are sharing. (eg. Adjust size for pixel-perfect // mirroring over HDMI.) DisplayDeviceInfo deviceInfo = mPrimaryDisplayDevice.getDisplayDeviceInfoLocked(); - if (!Objects.equals(mPrimaryDisplayDeviceInfo, deviceInfo)) { + if (!Objects.equals(mPrimaryDisplayDeviceInfo, deviceInfo) || mDirty) { mBaseDisplayInfo.layerStack = mLayerStack; mBaseDisplayInfo.flags = 0; + // Displays default to moving content to the primary display when removed + mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY; if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_SUPPORTS_PROTECTED_BUFFERS; } @@ -443,12 +452,20 @@ final class LogicalDisplay { mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation; mBaseDisplayInfo.displayShape = deviceInfo.displayShape; - if (mPosition == Layout.Display.POSITION_REAR) { + if (mDevicePosition == Layout.Display.POSITION_REAR) { + // A rear display is meant to host a specific experience that is essentially + // a presentation to another user or users other than the main user since they + // can't actually see that display. Given that, it's a suitable display for + // presentations but the content should be destroyed rather than moved to a non-rear + // display when the rear display is removed. mBaseDisplayInfo.flags |= Display.FLAG_REAR; + mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION; + mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT; } mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); + mDirty = false; } } @@ -857,7 +874,7 @@ final class LogicalDisplay { pw.println("mIsEnabled=" + mIsEnabled); pw.println("mIsInTransition=" + mIsInTransition); pw.println("mLayerStack=" + mLayerStack); - pw.println("mPosition=" + mPosition); + pw.println("mPosition=" + mDevicePosition); pw.println("mHasContent=" + mHasContent); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); pw.println("mRequestedColorMode=" + mRequestedColorMode); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 56c9056b1f0a..c69586279a49 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -992,7 +992,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { newDisplay.swapDisplaysLocked(oldDisplay); } - newDisplay.setPositionLocked(displayLayout.getPosition()); + newDisplay.setDevicePositionLocked(displayLayout.getPosition()); newDisplay.setLeadDisplayLocked(displayLayout.getLeadDisplayId()); setLayoutLimitedRefreshRate(newDisplay, device, displayLayout); setEnabledLocked(newDisplay, displayLayout.isEnabled()); diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 13fcff3805d3..e3d92a7b4831 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -92,9 +92,9 @@ public final class DisplayBrightnessController { // TODO: b/186428377 update brightness setting when display changes mBrightnessSetting = brightnessSetting; mPendingScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mScreenBrightnessDefault = BrightnessUtils.clampAbsoluteBrightness(defaultScreenBrightness); mCurrentScreenBrightness = getScreenBrightnessSetting(); mOnBrightnessChangeRunnable = onBrightnessChangeRunnable; - mScreenBrightnessDefault = BrightnessUtils.clampAbsoluteBrightness(defaultScreenBrightness); mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context, displayId); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 14b9121a9b1c..740d2a3c2b6e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -2481,6 +2481,7 @@ public class HdmiControlService extends SystemService { Slog.w(TAG, "Local tv device not available to change arc mode."); return; } + tv.startArcAction(enabled); } }); } diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 8ee3a721fc63..a113d011b457 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -103,9 +103,9 @@ class InputSettingsObserver extends ContentObserver { mObservers.get(uri).accept("setting changed"); } - private boolean getBoolean(String settingName) { + private boolean getBoolean(String settingName, boolean defaultValue) { final int setting = Settings.System.getIntForUser(mContext.getContentResolver(), - settingName, 0, UserHandle.USER_CURRENT); + settingName, defaultValue ? 1 : 0, UserHandle.USER_CURRENT); return setting != 0; } @@ -127,20 +127,21 @@ class InputSettingsObserver extends ContentObserver { private void updateTouchpadNaturalScrollingEnabled() { mNative.setTouchpadNaturalScrollingEnabled( - getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING)); + getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING, true)); } private void updateTouchpadTapToClickEnabled() { - mNative.setTouchpadTapToClickEnabled(getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK)); + mNative.setTouchpadTapToClickEnabled( + getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK, true)); } private void updateTouchpadRightClickZoneEnabled() { mNative.setTouchpadRightClickZoneEnabled( - getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE)); + getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, false)); } private void updateShowTouches() { - mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES)); + mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false)); } private void updateAccessibilityLargePointer() { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 0c99e865a040..b4fc19540e94 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -30,7 +30,6 @@ import static android.content.Context.KEYGUARD_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; -import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; @@ -1729,8 +1728,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (newCredential.isPattern()) { setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle); } - if (DeviceConfig.getBoolean(NAMESPACE_AUTO_PIN_CONFIRMATION, - "enable_auto_pin_confirmation", /* defaultValue= */ false)) { + if (LockPatternUtils.isAutoPinConfirmFeatureAvailable()) { if (newCredential.isPin()) { setLong(LockPatternUtils.PIN_LENGTH, newCredential.size(), userHandle); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index c5bcddc06298..6b9be2545c73 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1116,12 +1116,16 @@ public class LauncherAppsService extends SystemService { // Flag for bubble ActivityOptions options = ActivityOptions.fromBundle(startActivityOptions); - if (options != null && options.isApplyActivityFlagsForBubbles()) { - // Flag for bubble to make behaviour match documentLaunchMode=always. - intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); - intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + if (options != null) { + if (options.isApplyActivityFlagsForBubbles()) { + // Flag for bubble to make behaviour match documentLaunchMode=always. + intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); + intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + } + if (options.isApplyMultipleTaskFlagForShortcut()) { + intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + } } - intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intents[0].setSourceBounds(sourceBounds); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 8beb0b65da4e..002585f2e7d6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3069,7 +3069,7 @@ class PackageManagerShellCommand extends ShellCommand { getOutPrintWriter().printf("Success: user %d is already being removed\n", userId); return 0; case UserManager.REMOVE_RESULT_ERROR_MAIN_USER_PERMANENT_ADMIN: - getOutPrintWriter().printf("Error: user %d is a permanent admin main user\n", + getErrPrintWriter().printf("Error: user %d is a permanent admin main user\n", userId); return 1; default: diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index a9edce15a724..3cbaebe4101e 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -64,11 +64,12 @@ public abstract class UserManagerInternal { }) public @interface UserAssignmentResult {} - private static final String PREFIX_USER_START_MODE = "USER_START_MODE_"; + // TODO(b/248408342): Move keep annotation to the method referencing these fields reflectively. + @Keep public static final int USER_START_MODE_FOREGROUND = 1; + @Keep public static final int USER_START_MODE_BACKGROUND = 2; + @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3; - /** - * Type used to indicate how a user started. - */ + private static final String PREFIX_USER_START_MODE = "USER_START_MODE_"; @IntDef(flag = false, prefix = {PREFIX_USER_START_MODE}, value = { USER_START_MODE_FOREGROUND, USER_START_MODE_BACKGROUND, @@ -76,32 +77,6 @@ public abstract class UserManagerInternal { }) public @interface UserStartMode {} - // TODO(b/248408342): Move keep annotations below to the method referencing these fields - // reflectively. - - /** (Full) user started on foreground (a.k.a. "current user"). */ - @Keep public static final int USER_START_MODE_FOREGROUND = 1; - - /** - * User (full or profile) started on background and is - * {@link UserManager#isUserVisible() invisible}. - * - * <p>This is the "traditional" way of starting a background user, and can be used to start - * profiles as well, although starting an invisible profile is not common from the System UI - * (it could be done through APIs or adb, though). - */ - @Keep public static final int USER_START_MODE_BACKGROUND = 2; - - /** - * User (full or profile) started on background and is - * {@link UserManager#isUserVisible() visible}. - * - * <p>This is the "traditional" way of starting a profile (i.e., when the profile of the current - * user is the current foreground user), but it can also be used to start a full user associated - * with a display (which is the case on automotives with passenger displays). - */ - @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3; - public interface UserRestrictionsListener { /** * Called when a user restriction changes. diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index fe8a5008930a..d5cc7caffa90 100644 --- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java +++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java @@ -42,7 +42,6 @@ import android.util.Dumpable; import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.IntArray; -import android.util.Log; import android.util.SparseIntArray; import android.view.Display; @@ -56,8 +55,6 @@ import com.android.server.pm.UserManagerInternal.UserVisibilityListener; import com.android.server.utils.Slogf; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -80,11 +77,11 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public final class UserVisibilityMediator implements Dumpable { - private static final String TAG = UserVisibilityMediator.class.getSimpleName(); - - private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE + private static final String TAG = UserVisibilityMediator.class.getSimpleName(); + private static final String PREFIX_SECONDARY_DISPLAY_MAPPING = "SECONDARY_DISPLAY_MAPPING_"; public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1; public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2; @@ -101,7 +98,7 @@ public final class UserVisibilityMediator implements Dumpable { }) public @interface SecondaryDisplayMappingStatus {} - // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices + // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices @VisibleForTesting static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM; @@ -135,23 +132,10 @@ public final class UserVisibilityMediator implements Dumpable { private final SparseIntArray mExtraDisplaysAssignedToUsers; /** - * Mapping of each user that started visible (key) to its profile group id (value). - * - * <p>It's used to determine not just if the user is visible, but also - * {@link #isProfile(int, int) if it's a profile}. - */ - @GuardedBy("mLock") - private final SparseIntArray mStartedVisibleProfileGroupIds = new SparseIntArray(); - - /** - * List of profiles that have explicitly started invisible. - * - * <p>Only used for debugging purposes (and set when {@link #DBG} is {@code true}), hence we - * don't care about autoboxing. + * Mapping from each started user to its profile group. */ @GuardedBy("mLock") - @Nullable - private final List<Integer> mStartedInvisibleProfileUserIds; + private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray(); /** * Handler user to call listeners @@ -180,14 +164,9 @@ public final class UserVisibilityMediator implements Dumpable { mUsersAssignedToDisplayOnStart = null; mExtraDisplaysAssignedToUsers = null; } - mStartedInvisibleProfileUserIds = DBG ? new ArrayList<>(4) : null; mHandler = handler; - // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices - mStartedVisibleProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID); - - if (DBG) { - Slogf.i(TAG, "UserVisibilityMediator created with DBG on"); - } + // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices + mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID); } /** @@ -198,8 +177,6 @@ public final class UserVisibilityMediator implements Dumpable { int displayId) { Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d", userId); - validateUserStartMode(userStartMode); - // This method needs to perform 4 actions: // // 1. Check if the user can be started given the provided arguments @@ -247,29 +224,14 @@ public final class UserVisibilityMediator implements Dumpable { visibleUsersBefore = getVisibleUsers(); - // Set current user / started users state - switch (userStartMode) { - case USER_START_MODE_FOREGROUND: - mCurrentUserId = userId; - // Fallthrough - case USER_START_MODE_BACKGROUND_VISIBLE: - if (DBG) { - Slogf.d(TAG, "adding visible user / profile group id mapping (%d -> %d)", - userId, profileGroupId); - } - mStartedVisibleProfileGroupIds.put(userId, profileGroupId); - break; - case USER_START_MODE_BACKGROUND: - if (mStartedInvisibleProfileUserIds != null - && isProfile(userId, profileGroupId)) { - Slogf.d(TAG, "adding user %d to list of invisible profiles", userId); - mStartedInvisibleProfileUserIds.add(userId); - } - break; - default: - Slogf.wtf(TAG, "invalid userStartMode passed to assignUserToDisplayOnStart: " - + "%d", userStartMode); + // Set current user / profiles state + if (userStartMode == USER_START_MODE_FOREGROUND) { + mCurrentUserId = userId; } + if (DBG) { + Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId); + } + mStartedProfileGroupIds.put(userId, profileGroupId); // Set user / display state switch (mappingResult) { @@ -335,46 +297,39 @@ public final class UserVisibilityMediator implements Dumpable { boolean foreground = userStartMode == USER_START_MODE_FOREGROUND; if (displayId != DEFAULT_DISPLAY) { if (foreground) { - Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot start " + Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start " + "foreground user on secondary display", userId, profileGroupId, - userStartModeToString(userStartMode), displayId); + foreground, displayId); return USER_ASSIGNMENT_RESULT_FAILURE; } if (!mVisibleBackgroundUsersEnabled) { - Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: called on " + Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on " + "device that doesn't support multiple users on multiple displays", - userId, profileGroupId, userStartModeToString(userStartMode), displayId); + userId, profileGroupId, foreground, displayId); return USER_ASSIGNMENT_RESULT_FAILURE; } } if (isProfile(userId, profileGroupId)) { if (displayId != DEFAULT_DISPLAY) { - Slogf.w(TAG, "canStartUserLocked(%d, %d, %s, %d) failed: cannot start profile user " - + "on secondary display", userId, profileGroupId, - userStartModeToString(userStartMode), displayId); + Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user " + + "on secondary display", userId, profileGroupId, foreground, + displayId); return USER_ASSIGNMENT_RESULT_FAILURE; } - switch (userStartMode) { - case USER_START_MODE_FOREGROUND: - Slogf.w(TAG, "startUser(%d, %d, %s, %d) failed: cannot start profile user in " - + "foreground", userId, profileGroupId, - userStartModeToString(userStartMode), displayId); - return USER_ASSIGNMENT_RESULT_FAILURE; - case USER_START_MODE_BACKGROUND_VISIBLE: - boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId); - if (!isParentVisibleOnDisplay) { - Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot" - + " start profile user visible when its parent is not visible in " - + "that display", userId, profileGroupId, - userStartModeToString(userStartMode), displayId); - return USER_ASSIGNMENT_RESULT_FAILURE; - } - return USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE; - case USER_START_MODE_BACKGROUND: - return USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; + if (foreground) { + Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in " + + "foreground", userId, profileGroupId, foreground, displayId); + return USER_ASSIGNMENT_RESULT_FAILURE; + } else { + boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId); + if (DBG) { + Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay); + } + return isParentVisibleOnDisplay + ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE + : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; } - } return foreground || displayId != DEFAULT_DISPLAY @@ -392,9 +347,8 @@ public final class UserVisibilityMediator implements Dumpable { if (mVisibleBackgroundUserOnDefaultDisplayAllowed && userStartMode == USER_START_MODE_BACKGROUND_VISIBLE) { int userStartedOnDefaultDisplay = getUserStartedOnDisplay(DEFAULT_DISPLAY); - if (userStartedOnDefaultDisplay != USER_NULL - && userStartedOnDefaultDisplay != profileGroupId) { - Slogf.w(TAG, "canAssignUserToDisplayLocked(): cannot start user %d visible on" + if (userStartedOnDefaultDisplay != USER_NULL) { + Slogf.w(TAG, "getUserVisibilityOnStartLocked(): cannot start user %d visible on" + " default display because user %d already did so", userId, userStartedOnDefaultDisplay); return SECONDARY_DISPLAY_MAPPING_FAILED; @@ -500,7 +454,7 @@ public final class UserVisibilityMediator implements Dumpable { userId, displayId); return false; } - if (isStartedVisibleProfileLocked(userId)) { + if (isStartedProfile(userId)) { Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user is a profile", userId, displayId); return false; @@ -588,14 +542,10 @@ public final class UserVisibilityMediator implements Dumpable { @GuardedBy("mLock") private void unassignUserFromAllDisplaysOnStopLocked(@UserIdInt int userId) { if (DBG) { - Slogf.d(TAG, "Removing %d from mStartedVisibleProfileGroupIds (%s)", userId, - mStartedVisibleProfileGroupIds); - } - mStartedVisibleProfileGroupIds.delete(userId); - if (mStartedInvisibleProfileUserIds != null) { - Slogf.d(TAG, "Removing %d from list of invisible profiles", userId); - mStartedInvisibleProfileUserIds.remove(Integer.valueOf(userId)); + Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId, + mStartedProfileGroupIds); } + mStartedProfileGroupIds.delete(userId); if (!mVisibleBackgroundUsersEnabled) { // Don't need to update mUsersAssignedToDisplayOnStart because methods (such as @@ -625,8 +575,7 @@ public final class UserVisibilityMediator implements Dumpable { * See {@link UserManagerInternal#isUserVisible(int)}. */ public boolean isUserVisible(@UserIdInt int userId) { - // For optimization (as most devices don't support visible background users), check for - // current foreground user and their profiles first + // First check current foreground user and their profiles (on main display) if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) { if (VERBOSE) { Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId); @@ -635,31 +584,19 @@ public final class UserVisibilityMediator implements Dumpable { } if (!mVisibleBackgroundUsersEnabled) { - if (VERBOSE) { - Slogf.v(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when" + if (DBG) { + Slogf.d(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when" + " device doesn't support visible background users", userId); } return false; } - + boolean visible; synchronized (mLock) { - int profileGroupId; - synchronized (mLock) { - profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); - } - if (isProfile(userId, profileGroupId)) { - return isUserAssignedToDisplayOnStartLocked(profileGroupId); - } - return isUserAssignedToDisplayOnStartLocked(userId); + visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0; } - } - - @GuardedBy("mLock") - private boolean isUserAssignedToDisplayOnStartLocked(@UserIdInt int userId) { - boolean visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0; - if (VERBOSE) { - Slogf.v(TAG, "isUserAssignedToDisplayOnStartLocked(%d): %b", userId, visible); + if (DBG) { + Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible); } return visible; } @@ -672,8 +609,7 @@ public final class UserVisibilityMediator implements Dumpable { return false; } - // For optimization (as most devices don't support visible background users), check for - // current user and profile first. Current user is always visible on: + // Current user is always visible on: // - Default display // - Secondary displays when device doesn't support visible bg users // - Or when explicitly added (which is checked below) @@ -695,26 +631,14 @@ public final class UserVisibilityMediator implements Dumpable { } synchronized (mLock) { - int profileGroupId; - synchronized (mLock) { - profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); - } - if (isProfile(userId, profileGroupId)) { - return isFullUserVisibleOnBackgroundLocked(profileGroupId, displayId); + if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) { + // User assigned to display on start + return true; } - return isFullUserVisibleOnBackgroundLocked(userId, displayId); - } - } - // NOTE: it doesn't check if the userId is a full user, it's up to the caller to check that - @GuardedBy("mLock") - private boolean isFullUserVisibleOnBackgroundLocked(@UserIdInt int userId, int displayId) { - if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) { - // User assigned to display on start - return true; + // Check for extra display assignment + return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId; } - // Check for extra display assignment - return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId; } /** @@ -782,7 +706,7 @@ public final class UserVisibilityMediator implements Dumpable { continue; } int userId = mUsersAssignedToDisplayOnStart.keyAt(i); - if (!isStartedVisibleProfileLocked(userId)) { + if (!isStartedProfile(userId)) { return userId; } else if (DBG) { Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's " @@ -815,8 +739,8 @@ public final class UserVisibilityMediator implements Dumpable { // number of users is too small, the gain is probably not worth the increase on complexity. IntArray visibleUsers = new IntArray(); synchronized (mLock) { - for (int i = 0; i < mStartedVisibleProfileGroupIds.size(); i++) { - int userId = mStartedVisibleProfileGroupIds.keyAt(i); + for (int i = 0; i < mStartedProfileGroupIds.size(); i++) { + int userId = mStartedProfileGroupIds.keyAt(i); if (isUserVisible(userId)) { visibleUsers.add(userId); } @@ -849,7 +773,7 @@ public final class UserVisibilityMediator implements Dumpable { } } - // TODO(b/266158156): remove this method if not needed anymore + // TODO(b/242195409): remove this method if not needed anymore /** * Nofify all listeners that the system user visibility changed. */ @@ -911,9 +835,6 @@ public final class UserVisibilityMediator implements Dumpable { ipw.println("UserVisibilityMediator"); ipw.increaseIndent(); - ipw.print("DBG: "); - ipw.println(DBG); - synchronized (mLock) { ipw.print("Current user id: "); ipw.println(mCurrentUserId); @@ -921,12 +842,8 @@ public final class UserVisibilityMediator implements Dumpable { ipw.print("Visible users: "); ipw.println(getVisibleUsers()); - dumpSparseIntArray(ipw, mStartedVisibleProfileGroupIds, - "started visible user / profile group", "u", "pg"); - if (mStartedInvisibleProfileUserIds != null) { - ipw.print("Profiles started invisible: "); - ipw.println(mStartedInvisibleProfileUserIds); - } + dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group", + "u", "pg"); ipw.print("Supports visible background users on displays: "); ipw.println(mVisibleBackgroundUsersEnabled); @@ -1034,25 +951,22 @@ public final class UserVisibilityMediator implements Dumpable { if (mCurrentUserId == userId) { return true; } - return mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) - == mCurrentUserId; + return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId; } } - @GuardedBy("mLock") - private boolean isStartedVisibleProfileLocked(@UserIdInt int userId) { - int profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); + private boolean isStartedProfile(@UserIdInt int userId) { + int profileGroupId; + synchronized (mLock) { + profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); + } return isProfile(userId, profileGroupId); } - private void validateUserStartMode(@UserStartMode int userStartMode) { - switch (userStartMode) { - case USER_START_MODE_FOREGROUND: - case USER_START_MODE_BACKGROUND: - case USER_START_MODE_BACKGROUND_VISIBLE: - return; + private @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) { + synchronized (mLock) { + return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); } - throw new IllegalArgumentException("Invalid user start mode: " + userStartMode); } private static String secondaryDisplayMappingStatusToString( diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index 2fbf3fbc6881..751f53526ab0 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -79,7 +79,12 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat private static final long MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS = 750; // Delay for clearing out battery stats for UIDs corresponding to a removed user - public static final int UID_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 10_000; + public static final int UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 2_000; + + // Delay for the _final_ clean-up of battery stats after a user removal - just in case + // some UIDs took longer than UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS to + // stop running. + public static final int UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS = 10_000; private final ScheduledExecutorService mExecutorService = Executors.newSingleThreadScheduledExecutor( @@ -336,11 +341,20 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat @Override public Future<?> scheduleCleanupDueToRemovedUser(int userId) { synchronized (BatteryExternalStatsWorker.this) { + // Initial quick clean-up after a user removal + mExecutorService.schedule(() -> { + synchronized (mStats) { + mStats.clearRemovedUserUidsLocked(userId); + } + }, UID_QUICK_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS); + + // Final clean-up after a user removal, to take care of UIDs that were running longer + // than expected return mExecutorService.schedule(() -> { synchronized (mStats) { mStats.clearRemovedUserUidsLocked(userId); } - }, UID_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS); + }, UID_FINAL_REMOVAL_AFTER_USER_REMOVAL_DELAY_MILLIS, TimeUnit.MILLISECONDS); } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 5521384bb169..ec052ecd20f2 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -21,7 +21,6 @@ import android.app.ITransientNotificationCallback; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.Bundle; import android.os.IBinder; -import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; @@ -162,11 +161,10 @@ public interface StatusBarManagerInternal { LetterboxDetails[] letterboxDetails); /** @see com.android.internal.statusbar.IStatusBar#showTransient */ - void showTransient(int displayId, @InternalInsetsType int[] types, - boolean isGestureOnSystemBar); + void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar); /** @see com.android.internal.statusbar.IStatusBar#abortTransient */ - void abortTransient(int displayId, @InternalInsetsType int[] types); + void abortTransient(int displayId, @InsetsType int types); /** * @see com.android.internal.statusbar.IStatusBar#showToast(String, IBinder, CharSequence, diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 83f4805aca58..4489ba94235c 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -79,12 +79,10 @@ import android.service.notification.NotificationStats; import android.service.quicksettings.TileService; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; -import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsController.Appearance; @@ -645,7 +643,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void showTransient(int displayId, @InternalInsetsType int[] types, + public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) { getUiState(displayId).showTransient(types); if (mBar != null) { @@ -656,7 +654,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void abortTransient(int displayId, @InternalInsetsType int[] types) { + public void abortTransient(int displayId, @InsetsType int types) { getUiState(displayId).clearTransient(types); if (mBar != null) { try { @@ -1258,7 +1256,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private static class UiState { private @Appearance int mAppearance = 0; private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0]; - private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>(); + private @InsetsType int mTransientBarTypes; private boolean mNavbarColorManagedByIme = false; private @Behavior int mBehavior; private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); @@ -1285,16 +1283,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D mLetterboxDetails = letterboxDetails; } - private void showTransient(@InternalInsetsType int[] types) { - for (int type : types) { - mTransientBarTypes.add(type); - } + private void showTransient(@InsetsType int types) { + mTransientBarTypes |= types; } - private void clearTransient(@InternalInsetsType int[] types) { - for (int type : types) { - mTransientBarTypes.remove(type); - } + private void clearTransient(@InsetsType int types) { + mTransientBarTypes &= ~types; } private int getDisabled1() { @@ -1410,16 +1404,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // TODO(b/118592525): Currently, status bar only works on the default display. // Make it aware of multi-display if needed. final UiState state = mDisplayUiState.get(DEFAULT_DISPLAY); - final int[] transientBarTypes = new int[state.mTransientBarTypes.size()]; - for (int i = 0; i < transientBarTypes.length; i++) { - transientBarTypes[i] = state.mTransientBarTypes.valueAt(i); - } return new RegisterStatusBarResult(icons, gatherDisableActionsLocked(mCurrentUserId, 1), state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis, state.mImeBackDisposition, state.mShowImeSwitcher, gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken, state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes, - state.mPackageName, transientBarTypes, state.mLetterboxDetails); + state.mPackageName, state.mTransientBarTypes, state.mLetterboxDetails); } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 65127e4a3b31..8ce8c46d1939 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -342,6 +342,18 @@ final class AccessibilityController { // Not relevant for the window observer. } + void onWMTransition(int displayId, @WindowManager.TransitionType int type) { + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; type=" + type); + } + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.onWMTransition(displayId, type); + } + // Not relevant for the window observer. + } + void onWindowTransition(WindowState windowState, int transition) { if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { @@ -708,6 +720,28 @@ final class AccessibilityController { } } + void onWMTransition(int displayId, @WindowManager.TransitionType int type) { + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(LOG_TAG + ".onWMTransition", + FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; type=" + type); + } + if (DEBUG_WINDOW_TRANSITIONS) { + Slog.i(LOG_TAG, "Window transition: " + WindowManager.transitTypeToString(type) + + " displayId: " + displayId); + } + final boolean magnifying = mMagnifedViewport.isMagnifying(); + if (magnifying) { + // All opening/closing situations. + switch (type) { + case WindowManager.TRANSIT_OPEN: + case WindowManager.TRANSIT_TO_FRONT: + case WindowManager.TRANSIT_CLOSE: + case WindowManager.TRANSIT_TO_BACK: + mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED); + } + } + } + void onWindowTransition(WindowState windowState, int transition) { if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { mAccessibilityTracing.logTrace(LOG_TAG + ".onWindowTransition", diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 50eb356a0b37..c37a3d7f43ee 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1033,10 +1033,33 @@ class ActivityStarter { return err; } - boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, - requestCode, callingPid, callingUid, callingPackage, callingFeatureId, - request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord, - resultRootTask); + boolean abort; + try { + abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, + requestCode, callingPid, callingUid, callingPackage, callingFeatureId, + request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord, + resultRootTask); + } catch (SecurityException e) { + // Return activity not found for the explicit intent if the caller can't see the target + // to prevent the disclosure of package existence. + final Intent originalIntent = request.ephemeralIntent; + if (originalIntent != null && (originalIntent.getComponent() != null + || originalIntent.getPackage() != null)) { + final String targetPackageName = originalIntent.getComponent() != null + ? originalIntent.getComponent().getPackageName() + : originalIntent.getPackage(); + if (mService.getPackageManagerInternalLocked() + .filterAppAccess(targetPackageName, callingUid, userId)) { + if (resultRecord != null) { + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, + RESULT_CANCELED, null /* data */, null /* dataGrants */); + } + SafeActivityOptions.abort(options); + return ActivityManager.START_CLASS_NOT_FOUND; + } + } + throw e; + } abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index cd79f2ecbe79..e1fdeca167da 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -32,6 +32,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.BackgroundStartPrivileges; +import android.app.ComponentOptions; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; @@ -166,7 +167,8 @@ public class BackgroundActivityStartController { final boolean useCallingUidState = originatingPendingIntent == null || checkedOptions == null - || !checkedOptions.getIgnorePendingIntentCreatorForegroundState(); + || checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() + != ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; if (useCallingUidState) { if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d93a62d67f1f..9b643c5995a3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -47,8 +47,6 @@ import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; import static android.view.Display.STATE_UNKNOWN; import static android.view.Display.isSuspendedState; import static android.view.InsetsSource.ID_IME; -import static android.view.InsetsState.ITYPE_LEFT_GESTURES; -import static android.view.InsetsState.ITYPE_RIGHT_GESTURES; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -57,6 +55,7 @@ import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowInsets.Type.systemGestures; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; @@ -174,6 +173,7 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.ColorSpace; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -221,7 +221,6 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InsetsSource; import android.view.InsetsState; -import android.view.InsetsState.InternalInsetsType; import android.view.MagnificationSpec; import android.view.PrivacyIndicatorBounds; import android.view.RemoteAnimationDefinition; @@ -248,7 +247,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; -import com.android.internal.util.function.TriConsumer; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -466,6 +464,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private boolean mSystemGestureExclusionWasRestricted = false; private final Region mSystemGestureExclusionUnrestricted = new Region(); private int mSystemGestureExclusionLimit; + private final Rect mSystemGestureFrameLeft = new Rect(); + private final Rect mSystemGestureFrameRight = new Rect(); private Set<Rect> mRestrictedKeepClearAreas = new ArraySet<>(); private Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>(); @@ -1519,31 +1519,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayRotation; } - void setInsetProvider(@InternalInsetsType int type, WindowContainer win, - @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider) { - setInsetProvider(type, win, frameProvider, null /* overrideFrameProviders */); - } - - /** - * Marks a window as providing insets for the rest of the windows in the system. - * - * @param type The type of inset this window provides. - * @param win The window. - * @param frameProvider Function to compute the frame, or {@code null} if the just the frame of - * the window should be taken. Only for non-WindowState providers, nav bar - * and status bar. - * @param overrideFrameProviders Functions to compute the frame when dispatching insets to the - * given window types, or {@code null} if the normal frame should - * be taken. - */ - void setInsetProvider(@InternalInsetsType int type, WindowContainer win, - @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider, - @Nullable SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> - overrideFrameProviders) { - mInsetsStateController.getSourceProvider(type).setWindowContainer(win, frameProvider, - overrideFrameProviders); - } - InsetsStateController getInsetsStateController() { return mInsetsStateController; } @@ -4048,7 +4023,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int imePid = mInputMethodWindow.mSession.mPid; mAtmService.onImeWindowSetOnDisplayArea(imePid, mImeWindowsContainer); } - mInsetsStateController.getSourceProvider(ID_IME).setWindowContainer(win, + mInsetsStateController.getImeSourceProvider().setWindowContainer(win, mDisplayPolicy.getImeSourceFrameProvider(), null); computeImeTarget(true /* updateImeTarget */); updateImeControlTarget(); @@ -5719,10 +5694,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final Region unhandled = Region.obtain(); unhandled.set(0, 0, mDisplayFrames.mWidth, mDisplayFrames.mHeight); - final Rect leftEdge = mInsetsStateController.getSourceProvider(ITYPE_LEFT_GESTURES) - .getSource().getFrame(); - final Rect rightEdge = mInsetsStateController.getSourceProvider(ITYPE_RIGHT_GESTURES) - .getSource().getFrame(); + final InsetsState state = mInsetsStateController.getRawInsetsState(); + final Rect df = state.getDisplayFrame(); + final Insets gestureInsets = state.calculateInsets(df, systemGestures(), + false /* ignoreVisibility */); + mSystemGestureFrameLeft.set(df.left, df.top, gestureInsets.left, df.bottom); + mSystemGestureFrameRight.set(gestureInsets.right, df.top, df.right, df.bottom); final Region touchableRegion = Region.obtain(); final Region local = Region.obtain(); @@ -5766,25 +5743,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (needsGestureExclusionRestrictions(w, false /* ignoreRequest */)) { // Processes the region along the left edge. - remainingLeftRight[0] = addToGlobalAndConsumeLimit(local, outExclusion, leftEdge, - remainingLeftRight[0], w, EXCLUSION_LEFT); + remainingLeftRight[0] = addToGlobalAndConsumeLimit(local, outExclusion, + mSystemGestureFrameLeft, remainingLeftRight[0], w, EXCLUSION_LEFT); // Processes the region along the right edge. - remainingLeftRight[1] = addToGlobalAndConsumeLimit(local, outExclusion, rightEdge, - remainingLeftRight[1], w, EXCLUSION_RIGHT); + remainingLeftRight[1] = addToGlobalAndConsumeLimit(local, outExclusion, + mSystemGestureFrameRight, remainingLeftRight[1], w, EXCLUSION_RIGHT); // Adds the middle (unrestricted area) final Region middle = Region.obtain(local); - middle.op(leftEdge, Op.DIFFERENCE); - middle.op(rightEdge, Op.DIFFERENCE); + middle.op(mSystemGestureFrameLeft, Op.DIFFERENCE); + middle.op(mSystemGestureFrameRight, Op.DIFFERENCE); outExclusion.op(middle, Op.UNION); middle.recycle(); } else { boolean loggable = needsGestureExclusionRestrictions(w, true /* ignoreRequest */); if (loggable) { - addToGlobalAndConsumeLimit(local, outExclusion, leftEdge, + addToGlobalAndConsumeLimit(local, outExclusion, mSystemGestureFrameLeft, Integer.MAX_VALUE, w, EXCLUSION_LEFT); - addToGlobalAndConsumeLimit(local, outExclusion, rightEdge, + addToGlobalAndConsumeLimit(local, outExclusion, mSystemGestureFrameRight, Integer.MAX_VALUE, w, EXCLUSION_RIGHT); } outExclusion.op(local, Op.UNION); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e87680ac5a16..3c4d70650605 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -171,9 +171,8 @@ public class DisplayPolicy { /** Use the transit animation in style resource (see {@link #selectAnimation}). */ static final int ANIMATION_STYLEABLE = 0; - private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, - ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR}; - private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR}; + private static final int SHOW_TYPES_FOR_SWIPE = Type.statusBars() | Type.navigationBars(); + private static final int SHOW_TYPES_FOR_PANIC = Type.navigationBars(); private final WindowManagerService mService; private final Context mContext; @@ -251,7 +250,7 @@ public class DisplayPolicy { private boolean mIsFreeformWindowOverlappingWithNavBar; - private boolean mLastImmersiveMode; + private boolean mIsImmersiveMode; // The windows we were told about in focusChanged. private WindowState mFocusedWindow; @@ -1077,8 +1076,16 @@ public class DisplayPolicy { } else { overrideProviders = null; } - mDisplayContent.setInsetProvider(provider.type, win, frameProvider, - overrideProviders); + // TODO (b/234093736): Let InsetsFrameProvider have the following fields: + // - IBinder owner. + // - int index. + // - @InsetsType int type. + // So we can create the id by using InsetsSource#createId. + // And we won't need toPublicType anymore. + final int id = provider.type; + final @InsetsType int type = InsetsState.toPublicType(id); + mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(id, type) + .setWindowContainer(win, frameProvider, overrideProviders); mInsetsSourceWindowsExceptIme.add(win); } } @@ -1171,10 +1178,17 @@ public class DisplayPolicy { mLastFocusedWindow = null; } - final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources(); - for (int index = sources.size() - 1; index >= 0; index--) { - final @InternalInsetsType int type = sources.keyAt(index); - mDisplayContent.setInsetProvider(type, null /* win */, null /* frameProvider */); + if (win.hasInsetsSourceProvider()) { + final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders(); + final InsetsStateController controller = mDisplayContent.getInsetsStateController(); + for (int index = providers.size() - 1; index >= 0; index--) { + final InsetsSourceProvider provider = providers.valueAt(index); + provider.setWindowContainer( + null /* windowContainer */, + null /* frameProvider */, + null /* overrideFrameProviders */); + controller.removeSourceProvider(provider.getSource().getId()); + } } mInsetsSourceWindowsExceptIme.remove(win); } @@ -1243,7 +1257,6 @@ public class DisplayPolicy { * some temporal states, but doesn't change the window frames used to show on screen. */ void simulateLayoutDisplay(DisplayFrames displayFrames) { - final InsetsStateController controller = mDisplayContent.getInsetsStateController(); sTmpClientFrames.attachedFrame = null; for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) { final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i); @@ -1252,11 +1265,10 @@ public class DisplayPolicy { displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH, win.getRequestedVisibleTypes(), win.mGlobalScale, sTmpClientFrames); - final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources(); + final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders(); final InsetsState state = displayFrames.mInsetsState; - for (int index = sources.size() - 1; index >= 0; index--) { - final int type = sources.keyAt(index); - state.addSource(controller.getSourceProvider(type).createSimulatedSource( + for (int index = providers.size() - 1; index >= 0; index--) { + state.addSource(providers.valueAt(index).createSimulatedSource( displayFrames, sTmpClientFrames.frame)); } } @@ -1359,30 +1371,33 @@ public class DisplayPolicy { mIsFreeformWindowOverlappingWithNavBar = true; } - final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources(); - final Rect bounds = win.getBounds(); - for (int index = sources.size() - 1; index >= 0; index--) { - final InsetsSource source = sources.valueAt(index); - if ((source.getType() - & (Type.systemGestures() | Type.mandatorySystemGestures())) == 0) { - continue; - } - if (mLeftGestureHost != null && mTopGestureHost != null - && mRightGestureHost != null && mBottomGestureHost != null) { - continue; - } - final Insets insets = source.calculateInsets(bounds, false /* ignoreVisibility */); - if (mLeftGestureHost == null && insets.left > 0) { - mLeftGestureHost = win; - } - if (mTopGestureHost == null && insets.top > 0) { - mTopGestureHost = win; - } - if (mRightGestureHost == null && insets.right > 0) { - mRightGestureHost = win; - } - if (mBottomGestureHost == null && insets.bottom > 0) { - mBottomGestureHost = win; + if (win.hasInsetsSourceProvider()) { + final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders(); + final Rect bounds = win.getBounds(); + for (int index = providers.size() - 1; index >= 0; index--) { + final InsetsSourceProvider provider = providers.valueAt(index); + final InsetsSource source = provider.getSource(); + if ((source.getType() + & (Type.systemGestures() | Type.mandatorySystemGestures())) == 0) { + continue; + } + if (mLeftGestureHost != null && mTopGestureHost != null + && mRightGestureHost != null && mBottomGestureHost != null) { + continue; + } + final Insets insets = source.calculateInsets(bounds, false /* ignoreVisibility */); + if (mLeftGestureHost == null && insets.left > 0) { + mLeftGestureHost = win; + } + if (mTopGestureHost == null && insets.top > 0) { + mTopGestureHost = win; + } + if (mRightGestureHost == null && insets.right > 0) { + mRightGestureHost = win; + } + if (mBottomGestureHost == null && insets.bottom > 0) { + mBottomGestureHost = win; + } } } @@ -2171,14 +2186,27 @@ public class DisplayPolicy { appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible, freeformRootTaskVisible); + // Show immersive mode confirmation if needed. + final boolean wasImmersiveMode = mIsImmersiveMode; + final boolean isImmersiveMode = isImmersiveMode(win); + if (wasImmersiveMode != isImmersiveMode) { + mIsImmersiveMode = isImmersiveMode; + // The immersive confirmation window should be attached to the immersive window root. + final RootDisplayArea root = win.getRootDisplayArea(); + final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId; + mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, isImmersiveMode, + mService.mPolicy.isUserSetupComplete(), + isNavBarEmpty(disableFlags)); + } + + // Show transient bars for panic if needed. final boolean requestHideNavBar = !win.isRequestedVisible(Type.navigationBars()); final long now = SystemClock.uptimeMillis(); final boolean pendingPanic = mPendingPanicGestureUptime != 0 && now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION; final DisplayPolicy defaultDisplayPolicy = mService.getDefaultDisplayContentLocked().getDisplayPolicy(); - if (pendingPanic && requestHideNavBar && win != mNotificationShade - && getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR) + if (pendingPanic && requestHideNavBar && isImmersiveMode // TODO (b/111955725): Show keyguard presentation on all external displays && defaultDisplayPolicy.isKeyguardDrawComplete()) { // The user performed the panic gesture recently, we're about to hide the bars, @@ -2190,19 +2218,6 @@ public class DisplayPolicy { } } - // update navigation bar - boolean oldImmersiveMode = mLastImmersiveMode; - boolean newImmersiveMode = isImmersiveMode(win); - if (oldImmersiveMode != newImmersiveMode) { - mLastImmersiveMode = newImmersiveMode; - // The immersive confirmation window should be attached to the immersive window root. - final RootDisplayArea root = win.getRootDisplayArea(); - final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId; - mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, newImmersiveMode, - mService.mPolicy.isUserSetupComplete(), - isNavBarEmpty(disableFlags)); - } - return appearance; } @@ -2324,18 +2339,10 @@ public class DisplayPolicy { if (win == null) { return false; } - return getNavigationBar() != null - && canHideNavigationBar() - && getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR) - && win != getNotificationShade() - && !win.isActivityTypeDream(); - } - - /** - * @return whether the navigation bar can be hidden, e.g. the device has a navigation bar - */ - private boolean canHideNavigationBar() { - return hasNavigationBar(); + if (win == getNotificationShade() || win.isActivityTypeDream()) { + return false; + } + return getInsetsPolicy().hasHiddenSources(Type.navigationBars()); } private static boolean isNavBarEmpty(int systemUiFlags) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index bd821134f9b7..97dd2915c0f4 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -26,8 +26,6 @@ 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.InsetsState.ITYPE_NAVIGATION_BAR; -import static android.view.InsetsState.ITYPE_STATUS_BAR; 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; @@ -41,8 +39,6 @@ import android.app.StatusBarManager; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.res.Resources; -import android.util.ArrayMap; -import android.util.IntArray; import android.util.SparseArray; import android.view.InsetsAnimationControlCallbacks; import android.view.InsetsAnimationControlImpl; @@ -52,7 +48,6 @@ import android.view.InsetsFrameProvider; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InsetsState.InternalInsetsType; import android.view.InternalInsetsAnimationController; import android.view.SurfaceControl; import android.view.SyncRtSurfaceTransactionApplier; @@ -81,7 +76,6 @@ class InsetsPolicy { private final InsetsStateController mStateController; private final DisplayContent mDisplayContent; private final DisplayPolicy mPolicy; - private final IntArray mShowingTransientTypes = new IntArray(); /** For resetting visibilities of insets sources. */ private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() { @@ -95,7 +89,7 @@ class InsetsPolicy { return; } for (InsetsSourceControl control : controls) { - if (mShowingTransientTypes.indexOf(control.getId()) != -1) { + if (isTransient(control.getType())) { // The visibilities of transient bars will be handled with animations. continue; } @@ -117,13 +111,16 @@ class InsetsPolicy { }; private WindowState mFocusedWin; - private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR); - private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR); + 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; + /** * Let remote insets controller control system bars regardless of other settings. */ private boolean mRemoteInsetsControllerControlsSystemBars; + private final boolean mHideNavBarForKeyboard; private final float[] mTmpFloat9 = new float[9]; @@ -178,37 +175,46 @@ class InsetsPolicy { mNavBar.updateVisibility(navControlTarget, Type.navigationBars()); } - boolean isHidden(@InternalInsetsType int type) { - final WindowContainerInsetsSourceProvider provider = mStateController - .peekSourceProvider(type); - return provider != null && provider.hasWindowContainer() - && !provider.getSource().isVisible(); + boolean hasHiddenSources(@InsetsType int types) { + final InsetsState state = mStateController.getRawInsetsState(); + for (int i = state.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = state.sourceAt(i); + if ((source.getType() & types) == 0) { + continue; + } + if (!source.getFrame().isEmpty() && !source.isVisible()) { + return true; + } + } + return false; } - void showTransient(@InternalInsetsType int[] types, boolean isGestureOnSystemBar) { - boolean changed = false; - for (int i = types.length - 1; i >= 0; i--) { - final @InternalInsetsType int type = types[i]; - if (!isHidden(type)) { + void showTransient(@InsetsType int types, boolean isGestureOnSystemBar) { + @InsetsType int showingTransientTypes = mShowingTransientTypes; + final InsetsState rawState = mStateController.getRawInsetsState(); + for (int i = rawState.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = rawState.sourceAt(i); + if (source.isVisible()) { continue; } - if (mShowingTransientTypes.indexOf(type) != -1) { + final @InsetsType int type = source.getType(); + if ((source.getType() & types) == 0) { continue; } - mShowingTransientTypes.add(type); - changed = true; + showingTransientTypes |= type; } - if (changed) { + if (mShowingTransientTypes != showingTransientTypes) { + mShowingTransientTypes = showingTransientTypes; StatusBarManagerInternal statusBarManagerInternal = mPolicy.getStatusBarManagerInternal(); if (statusBarManagerInternal != null) { statusBarManagerInternal.showTransient(mDisplayContent.getDisplayId(), - mShowingTransientTypes.toArray(), isGestureOnSystemBar); + showingTransientTypes, isGestureOnSystemBar); } updateBarControlTarget(mFocusedWin); dispatchTransientSystemBarsVisibilityChanged( mFocusedWin, - isTransient(ITYPE_STATUS_BAR) || isTransient(ITYPE_NAVIGATION_BAR), + (showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0, isGestureOnSystemBar); // The leashes can be created while updating bar control target. The surface transaction @@ -224,7 +230,7 @@ class InsetsPolicy { } void hideTransient() { - if (mShowingTransientTypes.size() == 0) { + if (mShowingTransientTypes == 0) { return; } @@ -235,20 +241,25 @@ class InsetsPolicy { startAnimation(false /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { - for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) { + final SparseArray<WindowContainerInsetsSourceProvider> providers = + mStateController.getSourceProviders(); + for (int i = providers.size() - 1; i >= 0; i--) { + final WindowContainerInsetsSourceProvider 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. - final @InternalInsetsType int type = mShowingTransientTypes.get(i); - mStateController.getSourceProvider(type).setClientVisible(false); + provider.setClientVisible(false); } - mShowingTransientTypes.clear(); + mShowingTransientTypes = 0; updateBarControlTarget(mFocusedWin); } }); } - boolean isTransient(@InternalInsetsType int type) { - return mShowingTransientTypes.indexOf(type) != -1; + boolean isTransient(@InsetsType int type) { + return (mShowingTransientTypes & type) != 0; } /** @@ -280,9 +291,9 @@ class InsetsPolicy { ? token.getFixedRotationTransformInsetsState() : mStateController.getRawInsetsState(); outInsetsState.set(srcState, true /* copySources */); - for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) { - final InsetsSource source = outInsetsState.peekSource(mShowingTransientTypes.get(i)); - if (source != null) { + for (int i = outInsetsState.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = outInsetsState.sourceAt(i); + if (isTransient(source.getType())) { source.setVisible(false); } } @@ -333,8 +344,8 @@ class InsetsPolicy { } } - final ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController - .getSourceProviders(); + final SparseArray<WindowContainerInsetsSourceProvider> providers = + mStateController.getSourceProviders(); final int windowType = attrs.type; for (int i = providers.size() - 1; i >= 0; i--) { final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i); @@ -365,18 +376,17 @@ class InsetsPolicy { private InsetsState adjustVisibilityForTransientTypes(InsetsState originalState) { InsetsState state = originalState; - for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) { - final @InternalInsetsType int type = mShowingTransientTypes.get(i); - final InsetsSource originalSource = state.peekSource(type); - if (originalSource != null && originalSource.isVisible()) { + for (int i = state.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = state.sourceAt(i); + if (isTransient(source.getType()) && source.isVisible()) { if (state == originalState) { // The source will be modified, create a non-deep copy to store the new one. state = new InsetsState(originalState); } // Replace the source with a copy in invisible state. - final InsetsSource source = new InsetsSource(originalSource); - source.setVisible(false); - state.addSource(source); + final InsetsSource outSource = new InsetsSource(source); + outSource.setVisible(false); + state.addSource(outSource); } } return state; @@ -385,18 +395,23 @@ class InsetsPolicy { private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState, boolean copyState) { if (w.mIsImWindow) { + InsetsState state = originalState; // If navigation bar is not hidden by IME, IME should always receive visible // navigation bar insets. final boolean navVisible = !mHideNavBarForKeyboard; - final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR); - if (originalNavSource != null && originalNavSource.isVisible() != navVisible) { - final InsetsState state = copyState ? new InsetsState(originalState) - : originalState; - final InsetsSource navSource = new InsetsSource(originalNavSource); + for (int i = originalState.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = originalState.sourceAt(i); + if (source.getType() != Type.navigationBars() || source.isVisible() == navVisible) { + continue; + } + if (state == originalState && copyState) { + state = new InsetsState(originalState); + } + final InsetsSource navSource = new InsetsSource(source); navSource.setVisible(navVisible); state.addSource(navSource); - return state; } + return state; } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) { // During switching tasks with gestural navigation, before the next IME input target // starts the input, we should adjust and freeze the last IME visibility of the window @@ -447,23 +462,22 @@ class InsetsPolicy { * @param caller who changed the insets state. */ private void checkAbortTransient(InsetsControlTarget caller) { - if (mShowingTransientTypes.size() != 0) { - final IntArray abortTypes = new IntArray(); - final boolean imeRequestedVisible = caller.isRequestedVisible(Type.ime()); - for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) { - final @InternalInsetsType int type = mShowingTransientTypes.get(i); - if ((mStateController.isFakeTarget(type, caller) - && caller.isRequestedVisible(InsetsState.toPublicType(type))) - || (type == ITYPE_NAVIGATION_BAR && imeRequestedVisible)) { - mShowingTransientTypes.remove(i); - abortTypes.add(type); - } - } - StatusBarManagerInternal statusBarManagerInternal = - mPolicy.getStatusBarManagerInternal(); - if (abortTypes.size() > 0 && statusBarManagerInternal != null) { - statusBarManagerInternal.abortTransient( - mDisplayContent.getDisplayId(), abortTypes.toArray()); + if (mShowingTransientTypes == 0) { + return; + } + final boolean isImeVisible = mStateController.getImeSourceProvider().isClientVisible(); + final @InsetsType int fakeControllingTypes = + mStateController.getFakeControllingTypes(caller); + final @InsetsType int abortTypes = + (fakeControllingTypes & caller.getRequestedVisibleTypes()) + | (isImeVisible ? Type.navigationBars() : 0); + mShowingTransientTypes &= ~abortTypes; + if (abortTypes != 0) { + mDisplayContent.setLayoutNeeded(); + mDisplayContent.mWmService.requestTraversal(); + final StatusBarManagerInternal statusBarManager = mPolicy.getStatusBarManagerInternal(); + if (statusBarManager != null) { + statusBarManager.abortTransient(mDisplayContent.getDisplayId(), abortTypes); } } } @@ -473,12 +487,16 @@ class InsetsPolicy { * updateBarControlTarget(mFocusedWin) after this invocation. */ private void abortTransient() { - StatusBarManagerInternal statusBarManagerInternal = mPolicy.getStatusBarManagerInternal(); - if (statusBarManagerInternal != null) { - statusBarManagerInternal.abortTransient( - mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray()); + if (mShowingTransientTypes == 0) { + return; + } + final StatusBarManagerInternal statusBarManager = mPolicy.getStatusBarManagerInternal(); + if (statusBarManager != null) { + statusBarManager.abortTransient(mDisplayContent.getDisplayId(), mShowingTransientTypes); } - mShowingTransientTypes.clear(); + mShowingTransientTypes = 0; + mDisplayContent.setLayoutNeeded(); + mDisplayContent.mWmService.requestTraversal(); dispatchTransientSystemBarsVisibilityChanged( mFocusedWin, @@ -488,7 +506,7 @@ class InsetsPolicy { private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin, boolean fake) { - if (!fake && isShowingTransientTypes(Type.statusBars())) { + if (!fake && isTransient(Type.statusBars())) { return mDummyControlTarget; } final WindowState notificationShade = mPolicy.getNotificationShade(); @@ -541,7 +559,7 @@ class InsetsPolicy { // configured to be hidden by the IME. return null; } - if (!fake && isShowingTransientTypes(Type.navigationBars())) { + if (!fake && isTransient(Type.navigationBars())) { return mDummyControlTarget; } if (focusedWin == mPolicy.getNotificationShade()) { @@ -577,16 +595,6 @@ class InsetsPolicy { return focusedWin; } - private boolean isShowingTransientTypes(@InsetsType int types) { - final IntArray showingTransientTypes = mShowingTransientTypes; - for (int i = showingTransientTypes.size() - 1; i >= 0; i--) { - if ((InsetsState.toPublicType(showingTransientTypes.get(i)) & types) != 0) { - return true; - } - } - return false; - } - /** * Determines whether the remote insets controller should take control of system bars for all * windows. @@ -622,21 +630,17 @@ class InsetsPolicy { @VisibleForTesting void startAnimation(boolean show, Runnable callback) { - int typesReady = 0; - final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); - final IntArray showingTransientTypes = mShowingTransientTypes; - for (int i = showingTransientTypes.size() - 1; i >= 0; i--) { - final int sourceId = showingTransientTypes.get(i); - final WindowContainerInsetsSourceProvider provider = - mStateController.getSourceProvider(sourceId); - final InsetsSourceControl control = provider.getControl(mDummyControlTarget); - if (control == null || control.getLeash() == null) { - continue; + @InsetsType int typesReady = 0; + final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>(); + final InsetsSourceControl[] controls = + mStateController.getControlsForDispatch(mDummyControlTarget); + for (InsetsSourceControl control : controls) { + if (isTransient(control.getType()) && control.getLeash() != null) { + typesReady |= control.getType(); + controlsReady.put(control.getId(), new InsetsSourceControl(control)); } - typesReady |= control.getType(); - controls.put(sourceId, new InsetsSourceControl(control)); } - controlAnimationUnchecked(typesReady, controls, show, callback); + controlAnimationUnchecked(typesReady, controlsReady, show, callback); } private void controlAnimationUnchecked(int typesReady, diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index f5af2929c2bd..2b7a45144200 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -164,7 +164,7 @@ abstract class InsetsSourceProvider { // TODO: Ideally, we should wait for the animation to finish so previous window can // animate-out as new one animates-in. mWindowContainer.cancelAnimation(); - mWindowContainer.getProvidedInsetsSources().remove(mSource.getId()); + mWindowContainer.getInsetsSourceProviders().remove(mSource.getId()); mSeamlessRotating = false; } ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s", @@ -180,7 +180,7 @@ abstract class InsetsSourceProvider { mSource.setInsetsRoundedCornerFrame(false); mSourceFrame.setEmpty(); } else { - mWindowContainer.getProvidedInsetsSources().put(mSource.getId(), mSource); + mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this); if (mControllable) { mWindowContainer.setControllableInsetProvider(this); if (mPendingControlTarget != null) { @@ -192,13 +192,6 @@ abstract class InsetsSourceProvider { } /** - * @return Whether there is a window container which backs this source. - */ - boolean hasWindowContainer() { - return mWindowContainer != null; - } - - /** * The source frame can affect the layout of other windows, so this should be called once the * window container gets laid out. */ @@ -363,9 +356,9 @@ abstract class InsetsSourceProvider { } /** - * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget) + * @see InsetsStateController#onControlTargetChanged */ - void updateControlForFakeTarget(@Nullable InsetsControlTarget fakeTarget) { + void updateFakeControlTarget(@Nullable InsetsControlTarget fakeTarget) { if (fakeTarget == mFakeControlTarget) { return; } @@ -570,6 +563,10 @@ abstract class InsetsSourceProvider { return mControlTarget; } + InsetsControlTarget getFakeControlTarget() { + return mFakeControlTarget; + } + boolean isClientVisible() { return mClientVisible; } @@ -609,15 +606,15 @@ abstract class InsetsSourceProvider { } if (mControlTarget != null) { pw.print(prefix + "mControlTarget="); - pw.println(mControlTarget.getWindow()); + pw.println(mControlTarget); } if (mPendingControlTarget != null) { pw.print(prefix + "mPendingControlTarget="); - pw.println(mPendingControlTarget.getWindow()); + pw.println(mPendingControlTarget); } if (mFakeControlTarget != null) { pw.print(prefix + "mFakeControlTarget="); - pw.println(mFakeControlTarget.getWindow()); + pw.println(mFakeControlTarget); } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index a3f62b2c5693..0e1e63e84250 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -18,10 +18,6 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.InsetsSource.ID_IME; -import static android.view.InsetsState.ITYPE_CLIMATE_BAR; -import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; -import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; -import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.mandatorySystemGestures; @@ -38,10 +34,11 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; +import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsets; +import android.view.WindowInsets.Type.InsetsType; import com.android.internal.protolog.common.ProtoLog; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -49,7 +46,6 @@ import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.PrintWriter; import java.util.ArrayList; import java.util.function.Consumer; -import java.util.function.Function; /** * Manages global window inset state in the system represented by {@link InsetsState}. @@ -60,14 +56,11 @@ class InsetsStateController { private final InsetsState mState = new InsetsState(); private final DisplayContent mDisplayContent; - private final ArrayMap<Integer, WindowContainerInsetsSourceProvider> mProviders = - new ArrayMap<>(); - private final ArrayMap<InsetsControlTarget, ArrayList<Integer>> mControlTargetTypeMap = - new ArrayMap<>(); - private final SparseArray<InsetsControlTarget> mTypeControlTargetMap = new SparseArray<>(); - - /** @see #onControlFakeTargetChanged */ - private final SparseArray<InsetsControlTarget> mTypeFakeControlTargetMap = new SparseArray<>(); + private final SparseArray<WindowContainerInsetsSourceProvider> mProviders = new SparseArray<>(); + private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>> + mControlTargetProvidersMap = new ArrayMap<>(); + private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>(); + private final SparseArray<InsetsControlTarget> mIdFakeControlTargetMap = new SparseArray<>(); private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>(); @@ -92,15 +85,8 @@ class InsetsStateController { } }; - private final Function<Integer, WindowContainerInsetsSourceProvider> mSourceProviderFunc; - InsetsStateController(DisplayContent displayContent) { mDisplayContent = displayContent; - mSourceProviderFunc = id -> (id == ID_IME) - ? new ImeInsetsSourceProvider(mState.getOrCreateSource( - id, ime()), this, mDisplayContent) - : new WindowContainerInsetsSourceProvider(mState.getOrCreateSource( - id, InsetsState.toPublicType(id)), this, mDisplayContent); } InsetsState getRawInsetsState() { @@ -108,39 +94,55 @@ class InsetsStateController { } @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) { - ArrayList<Integer> controlled = mControlTargetTypeMap.get(target); + final ArrayList<InsetsSourceProvider> controlled = mControlTargetProvidersMap.get(target); if (controlled == null) { return null; } final int size = controlled.size(); final InsetsSourceControl[] result = new InsetsSourceControl[size]; for (int i = 0; i < size; i++) { - result[i] = mProviders.get(controlled.get(i)).getControl(target); + result[i] = controlled.get(i).getControl(target); } return result; } - ArrayMap<Integer, WindowContainerInsetsSourceProvider> getSourceProviders() { + SparseArray<WindowContainerInsetsSourceProvider> getSourceProviders() { return mProviders; } /** * @return The provider of a specific source ID. */ - WindowContainerInsetsSourceProvider getSourceProvider(int id) { - return mProviders.computeIfAbsent(id, mSourceProviderFunc); + WindowContainerInsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) { + WindowContainerInsetsSourceProvider provider = mProviders.get(id); + if (provider != null) { + return provider; + } + final InsetsSource source = mState.getOrCreateSource(id, type); + provider = id == ID_IME + ? new ImeInsetsSourceProvider(source, this, mDisplayContent) + : new WindowContainerInsetsSourceProvider(source, this, mDisplayContent); + mProviders.put(id, provider); + return provider; } ImeInsetsSourceProvider getImeSourceProvider() { - return (ImeInsetsSourceProvider) getSourceProvider(ID_IME); + return (ImeInsetsSourceProvider) getOrCreateSourceProvider(ID_IME, ime()); + } + + void removeSourceProvider(int id) { + if (id != ID_IME) { + mState.removeSource(id); + mProviders.remove(id); + } } /** - * @return The provider of a specific type or null if we don't have it. + * @return The provider of a source ID or null if we don't have it. */ @Nullable - WindowContainerInsetsSourceProvider peekSourceProvider(@InternalInsetsType int type) { - return mProviders.get(type); + WindowContainerInsetsSourceProvider peekSourceProvider(int id) { + return mProviders.get(id); } /** @@ -208,8 +210,16 @@ class InsetsStateController { } } - boolean isFakeTarget(@InternalInsetsType int type, InsetsControlTarget target) { - return mTypeFakeControlTargetMap.get(type) == target; + @InsetsType int getFakeControllingTypes(InsetsControlTarget target) { + @InsetsType int types = 0; + for (int i = mProviders.size() - 1; i >= 0; i--) { + final InsetsSourceProvider provider = mProviders.valueAt(i); + final InsetsControlTarget fakeControlTarget = provider.getFakeControlTarget(); + if (target == fakeControlTarget) { + types |= provider.getSource().getType(); + } + } + return types; } void onImeControlTargetChanged(@Nullable InsetsControlTarget imeTarget) { @@ -217,7 +227,7 @@ class InsetsStateController { // Make sure that we always have a control target for the IME, even if the IME target is // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible. InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget; - onControlChanged(ID_IME, target); + onControlTargetChanged(getImeSourceProvider(), target, false /* fake */); ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s", target != null ? target.getWindow() : "null"); notifyPendingInsetsControlChanged(); @@ -235,101 +245,88 @@ class InsetsStateController { @Nullable InsetsControlTarget fakeStatusControlling, @Nullable InsetsControlTarget navControlling, @Nullable InsetsControlTarget fakeNavControlling) { - onControlChanged(ITYPE_STATUS_BAR, statusControlling); - onControlChanged(ITYPE_NAVIGATION_BAR, navControlling); - onControlChanged(ITYPE_CLIMATE_BAR, statusControlling); - onControlChanged(ITYPE_EXTRA_NAVIGATION_BAR, navControlling); - onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling); - onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling); - onControlFakeTargetChanged(ITYPE_CLIMATE_BAR, fakeStatusControlling); - onControlFakeTargetChanged(ITYPE_EXTRA_NAVIGATION_BAR, fakeNavControlling); + for (int i = mProviders.size() - 1; i >= 0; i--) { + final InsetsSourceProvider provider = mProviders.valueAt(i); + final @InsetsType int type = provider.getSource().getType(); + if (type == WindowInsets.Type.statusBars()) { + onControlTargetChanged(provider, statusControlling, false /* fake */); + onControlTargetChanged(provider, fakeStatusControlling, true /* fake */); + } else if (type == WindowInsets.Type.navigationBars()) { + onControlTargetChanged(provider, navControlling, false /* fake */); + onControlTargetChanged(provider, fakeNavControlling, true /* fake */); + } + } notifyPendingInsetsControlChanged(); } void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget, InsetsSourceProvider provider) { - removeFromControlMaps(previousControlTarget, provider.getSource().getId(), - false /* fake */); + removeFromControlMaps(previousControlTarget, provider, false /* fake */); } - private void onControlChanged(@InternalInsetsType int type, - @Nullable InsetsControlTarget target) { - final InsetsControlTarget previous = mTypeControlTargetMap.get(type); - if (target == previous) { - return; - } - final WindowContainerInsetsSourceProvider provider = mProviders.get(type); - if (provider == null) { + private void onControlTargetChanged(InsetsSourceProvider provider, + @Nullable InsetsControlTarget target, boolean fake) { + final InsetsControlTarget lastTarget = fake + ? mIdFakeControlTargetMap.get(provider.getSource().getId()) + : mIdControlTargetMap.get(provider.getSource().getId()); + if (target == lastTarget) { return; } if (!provider.isControllable()) { return; } - provider.updateControlForTarget(target, false /* force */); - target = provider.getControlTarget(); - if (previous != null) { - removeFromControlMaps(previous, type, false /* fake */); - mPendingControlChanged.add(previous); - } - if (target != null) { - addToControlMaps(target, type, false /* fake */); - mPendingControlChanged.add(target); - } - } + if (fake) { + // The fake target updated here will be used to pretend to the app that it's still under + // control of the bars while it's not really, but we still need to find out the apps + // intentions around showing/hiding. For example, when the transient bars are showing, + // and the fake target requests to show system bars, the transient state will be + // aborted. + provider.updateFakeControlTarget(target); + } else { + provider.updateControlForTarget(target, false /* force */); - /** - * The fake target saved here will be used to pretend to the app that it's still under control - * of the bars while it's not really, but we still need to find out the apps intentions around - * showing/hiding. For example, when the transient bars are showing, and the fake target - * requests to show system bars, the transient state will be aborted. - */ - void onControlFakeTargetChanged(@InternalInsetsType int type, - @Nullable InsetsControlTarget fakeTarget) { - final InsetsControlTarget previous = mTypeFakeControlTargetMap.get(type); - if (fakeTarget == previous) { - return; - } - final WindowContainerInsetsSourceProvider provider = mProviders.get(type); - if (provider == null) { - return; + // Get control target again in case the provider didn't accept the one we passed to it. + target = provider.getControlTarget(); + if (target == lastTarget) { + return; + } } - provider.updateControlForFakeTarget(fakeTarget); - if (previous != null) { - removeFromControlMaps(previous, type, true /* fake */); - mPendingControlChanged.add(previous); + if (lastTarget != null) { + removeFromControlMaps(lastTarget, provider, fake); + mPendingControlChanged.add(lastTarget); } - if (fakeTarget != null) { - addToControlMaps(fakeTarget, type, true /* fake */); - mPendingControlChanged.add(fakeTarget); + if (target != null) { + addToControlMaps(target, provider, fake); + mPendingControlChanged.add(target); } } private void removeFromControlMaps(@NonNull InsetsControlTarget target, - @InternalInsetsType int type, boolean fake) { - final ArrayList<Integer> array = mControlTargetTypeMap.get(target); + InsetsSourceProvider provider, boolean fake) { + final ArrayList<InsetsSourceProvider> array = mControlTargetProvidersMap.get(target); if (array == null) { return; } - array.remove((Integer) type); + array.remove(provider); if (array.isEmpty()) { - mControlTargetTypeMap.remove(target); + mControlTargetProvidersMap.remove(target); } if (fake) { - mTypeFakeControlTargetMap.remove(type); + mIdFakeControlTargetMap.remove(provider.getSource().getId()); } else { - mTypeControlTargetMap.remove(type); + mIdControlTargetMap.remove(provider.getSource().getId()); } } private void addToControlMaps(@NonNull InsetsControlTarget target, - @InternalInsetsType int type, boolean fake) { - final ArrayList<Integer> array = mControlTargetTypeMap.computeIfAbsent(target, - key -> new ArrayList<>()); - array.add(type); + InsetsSourceProvider provider, boolean fake) { + final ArrayList<InsetsSourceProvider> array = mControlTargetProvidersMap.computeIfAbsent( + target, key -> new ArrayList<>()); + array.add(provider); if (fake) { - mTypeFakeControlTargetMap.put(type, target); + mIdFakeControlTargetMap.put(provider.getSource().getId(), target); } else { - mTypeControlTargetMap.put(type, target); + mIdControlTargetMap.put(provider.getSource().getId(), target); } } @@ -351,7 +348,7 @@ class InsetsStateController { for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) { final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i); controlTarget.notifyInsetsControlChanged(); - if (mControlTargetTypeMap.containsKey(controlTarget)) { + if (mControlTargetProvidersMap.containsKey(controlTarget)) { // We only collect targets who get controls, not lose controls. newControlTargets.add(controlTarget); } @@ -377,10 +374,25 @@ class InsetsStateController { prefix = prefix + " "; mState.dump(prefix, pw); pw.println(prefix + "Control map:"); - for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) { + for (int i = mControlTargetProvidersMap.size() - 1; i >= 0; i--) { + final InsetsControlTarget controlTarget = mControlTargetProvidersMap.keyAt(i); pw.print(prefix + " "); - pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> " - + mTypeControlTargetMap.valueAt(i)); + pw.print(controlTarget); + pw.println(":"); + final ArrayList<InsetsSourceProvider> providers = mControlTargetProvidersMap.valueAt(i); + for (int j = providers.size() - 1; j >= 0; j--) { + final InsetsSourceProvider provider = providers.get(j); + if (provider != null) { + pw.print(prefix + " "); + if (controlTarget == provider.getFakeControlTarget()) { + pw.print("(fake) "); + } + pw.println(provider.getControl(controlTarget)); + } + } + } + if (mControlTargetProvidersMap.isEmpty()) { + pw.print(prefix + " none"); } pw.println(prefix + "InsetsSourceProviders:"); for (int i = mProviders.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 23439067fd88..110cce2ef254 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -385,9 +385,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } ProtoLog.d(WM_DEBUG_TASKS, "Comparing existing cls=%s /aff=%s to new cls=%s /aff=%s", - r.getTask().rootAffinity, mIntent.getComponent().flattenToShortString(), - mInfo.taskAffinity, (task.realActivity != null - ? task.realActivity.flattenToShortString() : "")); + (task.realActivity != null ? task.realActivity.flattenToShortString() : ""), + task.rootAffinity, mIntent.getComponent().flattenToShortString(), + mTaskAffinity); // TODO Refactor to remove duplications. Check if logic can be simplified. if (task.realActivity != null && task.realActivity.compareTo(cls) == 0 && Objects.equals(documentData, taskDocumentData)) { diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 98ca9aecc706..586077658a26 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -146,8 +146,8 @@ public class SafeActivityOptions { .setLaunchRootTask(options.getLaunchRootTask()) .setPendingIntentBackgroundActivityStartMode( options.getPendingIntentBackgroundActivityStartMode()) - .setIgnorePendingIntentCreatorForegroundState( - options.getIgnorePendingIntentCreatorForegroundState()); + .setPendingIntentCreatorBackgroundActivityStartMode( + options.getPendingIntentCreatorBackgroundActivityStartMode()); } /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ee11f685af73..1e53cc34daac 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -149,7 +149,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -171,6 +170,7 @@ import android.util.DisplayMetrics; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; +import android.view.InsetsSource; import android.view.InsetsState; import android.view.RemoteAnimationAdapter; import android.view.Surface; @@ -2833,14 +2833,13 @@ class Task extends TaskFragment { void adjustAnimationBoundsForTransition(Rect animationBounds) { TaskTransitionSpec spec = mWmService.mTaskTransitionSpec; if (spec != null) { - for (@InsetsState.InternalInsetsType int insetType : spec.animationBoundInsets) { - WindowContainerInsetsSourceProvider insetProvider = getDisplayContent() - .getInsetsStateController() - .getSourceProvider(insetType); - - Insets insets = insetProvider.getSource().calculateVisibleInsets( - animationBounds); - animationBounds.inset(insets); + final InsetsState state = + getDisplayContent().getInsetsStateController().getRawInsetsState(); + for (int id : spec.animationBoundInsets) { + final InsetsSource source = state.peekSource(id); + if (source != null) { + animationBounds.inset(source.calculateVisibleInsets(animationBounds)); + } } } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f0c099f9f8c7..e82dc82cf235 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -216,6 +216,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final TransitionController.Logger mLogger = new TransitionController.Logger(); + /** + * {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware + * of it). Currently, this happens before the display is ready since nothing can be seen yet. + */ + boolean mIsPlayerEnabled = true; + Transition(@TransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine) { mType = type; @@ -777,7 +783,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { * be called directly; use {@link TransitionController#finishTransition} instead. */ void finishTransition() { - if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { + if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) { Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); } @@ -1112,7 +1118,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { controller.setupStartTransaction(transaction); } buildFinishTransaction(mFinishTransaction, info.getRootLeash()); - if (mController.getTransitionPlayer() != null) { + if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) { mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay); try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, @@ -1128,11 +1134,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } catch (RemoteException e) { // If there's an exception when trying to send the mergedTransaction to the // client, we should finish and apply it here so the transactions aren't lost. - cleanUpOnFailure(); + postCleanupOnFailure(); + } + final AccessibilityController accessibilityController = + dc.mWmService.mAccessibilityController; + if (accessibilityController.hasCallbacks()) { + accessibilityController.onWMTransition(dc.getDisplayId(), mType); } } else { - // No player registered, so just finish/apply immediately - cleanUpOnFailure(); + // No player registered or it's not enabled, so just finish/apply immediately + if (!mIsPlayerEnabled) { + mLogger.mSendTimeNs = SystemClock.uptimeNanos(); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Apply and finish immediately" + + " because player is disabled for transition #%d .", mSyncId); + } + postCleanupOnFailure(); } mController.mLoggerHandler.post(mLogger::logOnSend); mOverrideOptions = null; @@ -1143,6 +1159,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { info.releaseAnimSurfaces(); } + private void postCleanupOnFailure() { + mController.mAtm.mH.post(() -> { + synchronized (mController.mAtm.mGlobalLock) { + cleanUpOnFailure(); + } + }); + } + /** * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call * this directly, it's designed to by called by {@link TransitionController} only. diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 18788bf3bb6c..2c23b5de403f 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -126,6 +126,13 @@ class TransitionController { final Handler mLoggerHandler = FgThread.getHandler(); + /** + * {@code true} While this waits for the display to become enabled (during boot). While waiting + * for the display, all core-initiated transitions will be "local". + * Note: This defaults to false so that it doesn't interfere with unit tests. + */ + boolean mIsWaitingForDisplayEnabled = false; + TransitionController(ActivityTaskManagerService atm, TaskSnapshotController taskSnapshotController, TransitionTracer transitionTracer) { @@ -486,6 +493,15 @@ class TransitionController { Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange) { + if (mIsWaitingForDisplayEnabled) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Disabling player for transition" + + " #%d because display isn't enabled yet", transition.getSyncId()); + transition.mIsPlayerEnabled = false; + transition.mLogger.mRequestTimeNs = SystemClock.uptimeNanos(); + mAtm.mH.post(() -> mAtm.mWindowOrganizerController.startTransition( + transition.getToken(), null)); + return transition; + } try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Requesting StartTransition: %s", transition); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index b06bdb10c7dd..71739807290d 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -170,9 +170,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< protected InsetsSourceProvider mControllableInsetProvider; /** - * The insets sources provided by this windowContainer. + * The {@link InsetsSourceProvider}s provided by this window container. */ - protected SparseArray<InsetsSource> mProvidedInsetsSources = null; + protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null; // List of children for this window container. List is in z-order as the children appear on // screen with the top-most window container at the tail of the list. @@ -366,7 +366,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * {@link WindowState#mMergedLocalInsetsSources} by visiting the entire hierarchy. * * {@link WindowState#mAboveInsetsState} is updated by visiting all the windows in z-order - * top-to-bottom manner and considering the {@link WindowContainer#mProvidedInsetsSources} + * top-to-bottom manner and considering the {@link WindowContainer#mInsetsSourceProviders} * provided by the {@link WindowState}s at the top. * {@link WindowState#updateAboveInsetsState(InsetsState, SparseArray, ArraySet)} visits the * IME container in the correct order to make sure the IME insets are passed correctly to the @@ -1035,11 +1035,21 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - public SparseArray<InsetsSource> getProvidedInsetsSources() { - if (mProvidedInsetsSources == null) { - mProvidedInsetsSources = new SparseArray<>(); + /** + * Returns {@code true} if this node provides insets. + */ + public boolean hasInsetsSourceProvider() { + return mInsetsSourceProviders != null; + } + + /** + * Returns {@link InsetsSourceProvider}s provided by this node. + */ + public SparseArray<InsetsSourceProvider> getInsetsSourceProviders() { + if (mInsetsSourceProviders == null) { + mInsetsSourceProviders = new SparseArray<>(); } - return mProvidedInsetsSources; + return mInsetsSourceProviders; } public DisplayContent getDisplayContent() { @@ -4102,13 +4112,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - private void hideInsetSourceViewOverflows(Set<Integer> insetTypes) { - final ArrayList<SurfaceControl> surfaceControls = - new ArrayList<>(insetTypes.size()); - - for (int insetType : insetTypes) { - WindowContainerInsetsSourceProvider insetProvider = getDisplayContent() - .getInsetsStateController().getSourceProvider(insetType); + private void hideInsetSourceViewOverflows(Set<Integer> sourceIds) { + final InsetsStateController controller = getDisplayContent().getInsetsStateController(); + for (int id : sourceIds) { + final InsetsSourceProvider insetProvider = controller.peekSourceProvider(id); + if (insetProvider == null) { + return; + } // Will apply it immediately to current leash and to all future inset animations // until we disable it. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b46a7203515c..06a886924f04 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -310,6 +310,7 @@ import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardLockedStateListener; import com.android.internal.policy.IShortcutService; import com.android.internal.policy.KeyInterceptionInfo; +import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.DumpUtils; @@ -3776,6 +3777,12 @@ public class WindowManagerService extends IWindowManager.Stub // Make sure the last requested orientation has been applied. updateRotationUnchecked(false, false); + + synchronized (mGlobalLock) { + mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Notified TransitionController " + + "that the display is ready."); + } } private boolean checkBootAnimationCompleteLocked() { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 2e9c9cf47efd..73e417e5ce9f 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -168,6 +168,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub void setWindowManager(WindowManagerService wms) { mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController, wms.mTransitionTracer); + mTransitionController.mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled; mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7b880fef276a..e8aa38efb08d 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1392,15 +1392,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void updateSourceFrame(Rect winFrame) { + if (!hasInsetsSourceProvider()) { + // This window doesn't provide any insets. + return; + } if (mGivenInsetsPending) { // The given insets are pending, and they are not reliable for now. The source frame // should be updated after the new given insets are sent to window manager. return; } - final SparseArray<InsetsSource> providedSources = getProvidedInsetsSources(); - final InsetsStateController controller = getDisplayContent().getInsetsStateController(); - for (int i = providedSources.size() - 1; i >= 0; i--) { - controller.getSourceProvider(providedSources.keyAt(i)).updateSourceFrame(winFrame); + final SparseArray<InsetsSourceProvider> providers = getInsetsSourceProviders(); + for (int i = providers.size() - 1; i >= 0; i--) { + providers.valueAt(i).updateSourceFrame(winFrame); } } @@ -1879,11 +1882,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean providesNonDecorInsets() { - if (mProvidedInsetsSources == null) { + if (mInsetsSourceProviders == null) { return false; } - for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) { - final InsetsSource source = mProvidedInsetsSources.valueAt(i); + for (int i = mInsetsSourceProviders.size() - 1; i >= 0; i--) { + final InsetsSource source = mInsetsSourceProviders.valueAt(i).getSource(); if (source.getType() == WindowInsets.Type.navigationBars()) { return true; } @@ -4840,10 +4843,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP insetsChangedWindows.add(w); } - final SparseArray<InsetsSource> providedSources = w.mProvidedInsetsSources; - if (providedSources != null) { - for (int i = providedSources.size() - 1; i >= 0; i--) { - aboveInsetsState.addSource(providedSources.valueAt(i)); + final SparseArray<InsetsSourceProvider> providers = w.mInsetsSourceProviders; + if (providers != null) { + for (int i = providers.size() - 1; i >= 0; i--) { + aboveInsetsState.addSource(providers.valueAt(i).getSource()); } } }, true /* traverseTopToBottom */); diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index 4cb7a8fc04de..dd757bc91415 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -29,8 +29,17 @@ #include <utils/Log.h> #include <map> +#include <set> #include <string> +/** + * Log debug messages about native virtual input devices. + * Enable this via "adb shell setprop log.tag.InputController DEBUG" + */ +static bool isDebug() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); +} + namespace android { enum class DeviceType { @@ -194,6 +203,15 @@ static std::map<int, int> KEY_CODE_MAPPING = { {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA}, }; +/* + * Map from the uinput touchscreen fd to the pointers present in the previous touch events that + * hasn't been lifted. + * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual + * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id to go + * up to MAX_POINTERS_ID. + */ +static std::map<int32_t, std::bitset<MAX_POINTERS>> unreleasedTouches; + /** Creates a new uinput device and assigns a file descriptor. */ static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys, DeviceType deviceType, jint screenHeight, jint screenWidth) { @@ -366,6 +384,12 @@ static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) { ioctl(fd, UI_DEV_DESTROY); + if (auto touchesOnFd = unreleasedTouches.find(fd); touchesOnFd != unreleasedTouches.end()) { + const size_t remainingPointers = touchesOnFd->second.size(); + unreleasedTouches.erase(touchesOnFd); + ALOGW_IF(remainingPointers > 0, "Closing touchscreen %d, erased %zu unreleased pointers.", + fd, remainingPointers); + } return close(fd); } @@ -425,6 +449,69 @@ static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jint fd, jint butt return true; } +static bool handleTouchUp(int fd, int pointerId) { + if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1))) { + return false; + } + auto touchesOnFd = unreleasedTouches.find(fd); + if (touchesOnFd == unreleasedTouches.end()) { + ALOGE("PointerId %d action UP received with no prior events on touchscreen %d.", pointerId, + fd); + return false; + } + ALOGD_IF(isDebug(), "Unreleased touches found for touchscreen %d in the map", fd); + + // When a pointer is no longer in touch, remove the pointer id from the corresponding + // entry in the unreleased touches map. + if (pointerId < 0 || pointerId >= MAX_POINTERS) { + ALOGE("Virtual touch event has invalid pointer id %d; value must be between 0 and %zu", + pointerId, MAX_POINTERS - 1); + return false; + } + if (!touchesOnFd->second.test(pointerId)) { + ALOGE("PointerId %d action UP received with no prior action DOWN on touchscreen %d.", + pointerId, fd); + return false; + } + touchesOnFd->second.reset(pointerId); + ALOGD_IF(isDebug(), "Pointer %d erased from the touchscreen %d", pointerId, fd); + + // Only sends the BTN UP event when there's no pointers on the touchscreen. + if (touchesOnFd->second.none()) { + unreleasedTouches.erase(touchesOnFd); + if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE))) { + return false; + } + ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", fd); + } + return true; +} + +static bool handleTouchDown(int fd, int pointerId) { + // When a new pointer is down on the touchscreen, add the pointer id in the corresponding + // entry in the unreleased touches map. + auto touchesOnFd = unreleasedTouches.find(fd); + if (touchesOnFd == unreleasedTouches.end()) { + // Only sends the BTN Down event when the first pointer on the touchscreen is down. + if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS))) { + return false; + } + touchesOnFd = unreleasedTouches.insert({fd, {}}).first; + ALOGD_IF(isDebug(), "New touchscreen with fd %d added in the unreleased touches map.", fd); + } + if (touchesOnFd->second.test(pointerId)) { + ALOGE("Repetitive action DOWN event received on a pointer %d that is already down.", + pointerId); + return false; + } + touchesOnFd->second.set(pointerId); + ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, fd); + if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId))) { + return false; + } + return true; +} + static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jint fd, jint pointerId, jint toolType, jint action, jfloat locationX, jfloat locationY, jfloat pressure, jfloat majorAxisSize) { @@ -446,15 +533,10 @@ static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jint fd, jint point return false; } UinputAction uinputAction = actionIterator->second; - if (uinputAction == UinputAction::PRESS || uinputAction == UinputAction::RELEASE) { - if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(uinputAction))) { - return false; - } - if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, - static_cast<int32_t>(uinputAction == UinputAction::PRESS ? pointerId - : -1))) { - return false; - } + if (uinputAction == UinputAction::PRESS && !handleTouchDown(fd, pointerId)) { + return false; + } else if (uinputAction == UinputAction::RELEASE && !handleTouchUp(fd, pointerId)) { + return false; } if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_X, locationX)) { return false; diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index be60946dc655..447c67fa44f8 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -23,6 +23,7 @@ import android.credentials.ClearCredentialStateRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ui.ProviderData; import android.credentials.ui.RequestInfo; +import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialProviderInfo; @@ -41,9 +42,9 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta public ClearRequestSession(Context context, int userId, int callingUid, IClearCredentialStateCallback callback, ClearCredentialStateRequest request, - CallingAppInfo callingAppInfo) { + CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) { super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED, - callingAppInfo); + callingAppInfo, cancellationSignal); } /** @@ -111,6 +112,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta private void respondToClientWithResponseAndFinish() { Log.i(TAG, "respondToClientWithResponseAndFinish"); + if (isSessionCancelled()) { + // TODO: Differentiate btw cancelled and false + logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true); + finishSession(/*propagateCancellation=*/true); + return; + } try { mClientCallback.onSuccess(); logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true); @@ -118,18 +125,24 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta Log.i(TAG, "Issue while propagating the response to the client"); logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false); } - finishSession(); + finishSession(/*propagateCancellation=*/false); } private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { Log.i(TAG, "respondToClientWithErrorAndFinish"); + if (isSessionCancelled()) { + // TODO: Differentiate btw cancelled and false + logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ true); + finishSession(/*propagateCancellation=*/true); + return; + } try { mClientCallback.onError(errorType, errorMsg); } catch (RemoteException e) { e.printStackTrace(); } logApiCalled(RequestType.CLEAR_CREDENTIALS, /* isSuccessful */ false); - finishSession(); + finishSession(/*propagateCancellation=*/false); } private void processResponses() { diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 351afb9b91d3..2345e3f5a3f0 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -27,6 +27,7 @@ import android.credentials.CredentialManager; import android.credentials.ICreateCredentialCallback; import android.credentials.ui.ProviderData; import android.credentials.ui.RequestInfo; +import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialProviderInfo; @@ -47,9 +48,10 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR CreateRequestSession(@NonNull Context context, int userId, int callingUid, CreateCredentialRequest request, ICreateCredentialCallback callback, - CallingAppInfo callingAppInfo) { + CallingAppInfo callingAppInfo, + CancellationSignal cancellationSignal) { super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE, - callingAppInfo); + callingAppInfo, cancellationSignal); } /** @@ -119,6 +121,12 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) { Log.i(TAG, "respondToClientWithResponseAndFinish"); + if (isSessionCancelled()) { + // TODO: Differentiate btw cancelled and false + logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true); + finishSession(/*propagateCancellation=*/true); + return; + } try { mClientCallback.onResponse(response); logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true); @@ -126,18 +134,24 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR Log.i(TAG, "Issue while responding to client: " + e.getMessage()); logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false); } - finishSession(); + finishSession(/*propagateCancellation=*/false); } private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { Log.i(TAG, "respondToClientWithErrorAndFinish"); + if (isSessionCancelled()) { + // TODO: Differentiate btw cancelled and false + logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ true); + finishSession(/*propagateCancellation=*/true); + return; + } try { mClientCallback.onError(errorType, errorMsg); } catch (RemoteException e) { Log.i(TAG, "Issue while responding to client: " + e.getMessage()); } logApiCalled(RequestType.CREATE_CREDENTIALS, /* isSuccessful */ false); - finishSession(); + finishSession(/*propagateCancellation=*/false); } @Override diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java index fbdcc441a419..3d504efd1797 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java +++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java @@ -19,35 +19,72 @@ package com.android.server.credentials; import android.credentials.CredentialDescription; import android.credentials.RegisterCredentialDescriptionRequest; import android.credentials.UnregisterCredentialDescriptionRequest; +import android.service.credentials.CredentialEntry; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; + import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; /** Contains information on what CredentialProvider has what provisioned Credential. */ -public class CredentialDescriptionRegistry { +public final class CredentialDescriptionRegistry { private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128; private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16; - private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser; + @GuardedBy("sLock") + private static final SparseArray<CredentialDescriptionRegistry> + sCredentialDescriptionSessionPerUser; + private static final ReentrantLock sLock; static { sCredentialDescriptionSessionPerUser = new SparseArray<>(); + sLock = new ReentrantLock(); + } + + /** Represents the results of a given query into the registry. */ + public static final class FilterResult { + final String mPackageName; + final List<CredentialEntry> mCredentialEntries; + + private FilterResult(String packageName, + List<CredentialEntry> credentialEntries) { + mPackageName = packageName; + mCredentialEntries = credentialEntries; + } } - // TODO(b/265992655): add a way to update CredentialRegistry when a user is removed. /** Get and/or create a {@link CredentialDescription} for the given user id. */ + @GuardedBy("sLock") public static CredentialDescriptionRegistry forUser(int userId) { - CredentialDescriptionRegistry session = - sCredentialDescriptionSessionPerUser.get(userId, null); + sLock.lock(); + try { + CredentialDescriptionRegistry session = + sCredentialDescriptionSessionPerUser.get(userId, null); + + if (session == null) { + session = new CredentialDescriptionRegistry(); + sCredentialDescriptionSessionPerUser.put(userId, session); + } + return session; + } finally { + sLock.unlock(); + } + } - if (session == null) { - session = new CredentialDescriptionRegistry(); - sCredentialDescriptionSessionPerUser.put(userId, session); + /** Clears an existing session for a given user identifier. */ + @GuardedBy("sLock") + public static void clearUserSession(int userId) { + sLock.lock(); + try { + sCredentialDescriptionSessionPerUser.remove(userId); + } finally { + sLock.unlock(); } - return session; } private Map<String, Set<CredentialDescription>> mCredentialDescriptions; @@ -74,7 +111,7 @@ public class CredentialDescriptionRegistry { int size = mCredentialDescriptions.get(callingPackageName).size(); mCredentialDescriptions.get(callingPackageName) .addAll(descriptions); - mTotalDescriptionCount += size - mCredentialDescriptions.get(callingPackageName).size(); + mTotalDescriptionCount += mCredentialDescriptions.get(callingPackageName).size() - size; } } @@ -93,21 +130,33 @@ public class CredentialDescriptionRegistry { } } - /** Returns package names of CredentialProviders that can satisfy a given + /** Returns package names and entries of a CredentialProviders that can satisfy a given * {@link CredentialDescription}. */ - public Set<String> filterCredentials(String flatRequestString) { + public Set<FilterResult> getFilteredResultForProvider(String packageName, + List<String> flatRequestStrings) { + Set<FilterResult> result = new HashSet<>(); + Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName); + for (CredentialDescription containedDescription: currentSet) { + if (flatRequestStrings.contains(containedDescription.getFlattenedRequestString())) { + result.add(new FilterResult(packageName, containedDescription + .getCredentialEntries())); + } + } + return result; + } + /** Returns package names of CredentialProviders that can satisfy a given + * {@link CredentialDescription}. */ + public Set<String> getMatchingProviders(Set<String> flatRequestString) { Set<String> result = new HashSet<>(); - - for (String componentName: mCredentialDescriptions.keySet()) { - Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName); - for (CredentialDescription containedDescription: currentSet) { - if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) { - result.add(componentName); + for (String packageName: mCredentialDescriptions.keySet()) { + Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName); + for (CredentialDescription containedDescription : currentSet) { + if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) { + result.add(packageName); } } } - return result; } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index ff72ed7abf4d..ea63c30a911c 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -61,13 +61,11 @@ import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.SecureSettingsServiceNameResolver; import java.util.ArrayList; -import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Entry point service for credential management. @@ -196,7 +194,6 @@ public final class CredentialManagerService } - @GuardedBy("mLock") private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock( int resolvedUserId) { @@ -236,6 +233,7 @@ public final class CredentialManagerService concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId)); return concatenatedServices; } + public static boolean isCredentialDescriptionApiEnabled() { return DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false); @@ -244,44 +242,38 @@ public final class CredentialManagerService @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked // to be guarded by 'service.mLock', which is the same as mLock. private List<ProviderSession> initiateProviderSessionsWithActiveContainers( - RequestSession session, - List<String> requestOptions, Set<ComponentName> activeCredentialContainers) { + GetRequestSession session, + List<String> requestOptions, Set<String> activeCredentialContainers) { List<ProviderSession> providerSessions = new ArrayList<>(); // Invoke all services of a user to initiate a provider session - runForUser((service) -> { - if (activeCredentialContainers.contains(service.getComponentName())) { - ProviderSession providerSession = service - .initiateProviderSessionForRequestLocked(session, requestOptions); - if (providerSession != null) { - providerSessions.add(providerSession); - } - } - }); + for (String packageName: activeCredentialContainers) { + providerSessions.add(ProviderRegistryGetSession.createNewSession( + mContext, + UserHandle.getCallingUserId(), + session, + packageName, + requestOptions)); + } return providerSessions; } @NonNull - private Set<String> getMatchingProviders(GetCredentialRequest request) { + private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) { // Session for active/provisioned credential descriptions; CredentialDescriptionRegistry registry = CredentialDescriptionRegistry .forUser(UserHandle.getCallingUserId()); // All requested credential descriptions based on the given request. Set<String> requestedCredentialDescriptions = - request.getCredentialOptions().stream().map( - credentialOption -> credentialOption + options.stream().map( + getCredentialOption -> getCredentialOption .getCredentialRetrievalData() .getString(CredentialOption .FLATTENED_REQUEST)) .collect(Collectors.toSet()); // All requested credential descriptions based on the given request. - return requestedCredentialDescriptions.stream() - .map(registry::filterCredentials) - .flatMap( - (Function<Set<String>, Stream<String>>) - Collection::stream) - .collect(Collectors.toSet()); + return registry.getMatchingProviders(requestedCredentialDescriptions); } @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked @@ -304,6 +296,13 @@ public final class CredentialManagerService return providerSessions; } + @Override + @GuardedBy("CredentialDescriptionRegistry.sLock") + public void onUserStopped(@NonNull TargetUser user) { + super.onUserStopped(user); + CredentialDescriptionRegistry.clearUserSession(user.getUserIdentifier()); + } + private CallingAppInfo constructCallingAppInfo(String packageName, int userId) { final PackageInfo packageInfo; try { @@ -338,15 +337,60 @@ public final class CredentialManagerService callingUid, callback, request, - constructCallingAppInfo(callingPackage, userId)); - - // Initiate all provider sessions - List<ProviderSession> providerSessions = - initiateProviderSessions( - session, - request.getCredentialOptions().stream() - .map(CredentialOption::getType) - .collect(Collectors.toList())); + constructCallingAppInfo(callingPackage, userId), + CancellationSignal.fromTransport(cancelTransport)); + + List<ProviderSession> providerSessions; + + if (isCredentialDescriptionApiEnabled()) { + List<CredentialOption> optionsThatRequireActiveCredentials = + request.getCredentialOptions().stream() + .filter(getCredentialOption -> + !TextUtils.isEmpty(getCredentialOption + .getCredentialRetrievalData().getString( + CredentialOption + .FLATTENED_REQUEST, null))) + .toList(); + + List<CredentialOption> optionsThatDoNotRequireActiveCredentials = + request.getCredentialOptions().stream() + .filter(getCredentialOption -> + TextUtils.isEmpty(getCredentialOption + .getCredentialRetrievalData().getString( + CredentialOption + .FLATTENED_REQUEST, null))) + .toList(); + + List<ProviderSession> sessionsWithoutRemoteService = + initiateProviderSessionsWithActiveContainers(session, + optionsThatRequireActiveCredentials + .stream().map(getCredentialOption -> + getCredentialOption.getCredentialRetrievalData() + .getString(CredentialOption + .FLATTENED_REQUEST)) + .collect(Collectors.toList()), + getFilteredResultFromRegistry(optionsThatRequireActiveCredentials)); + + List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions( + session, + optionsThatDoNotRequireActiveCredentials.stream() + .map(CredentialOption::getType) + .collect(Collectors.toList())); + + Set<ProviderSession> all = new LinkedHashSet<>(); + all.addAll(sessionsWithRemoteService); + all.addAll(sessionsWithoutRemoteService); + + providerSessions = new ArrayList<>(all); + } else { + // Initiate all provider sessions + providerSessions = + initiateProviderSessions( + session, + request.getCredentialOptions().stream() + .map(CredentialOption::getType) + .collect(Collectors.toList())); + } if (providerSessions.isEmpty()) { try { @@ -360,9 +404,8 @@ public final class CredentialManagerService + e.getMessage()); } } - - // Iterate over all provider sessions and invoke the request providerSessions.forEach(ProviderSession::invokeSession); + return cancelTransport; } @@ -385,7 +428,8 @@ public final class CredentialManagerService callingUid, request, callback, - constructCallingAppInfo(callingPackage, userId)); + constructCallingAppInfo(callingPackage, userId), + CancellationSignal.fromTransport(cancelTransport)); // Initiate all provider sessions List<ProviderSession> providerSessions = @@ -405,8 +449,7 @@ public final class CredentialManagerService } // Iterate over all provider sessions and invoke the request - providerSessions.forEach( - ProviderSession::invokeSession); + providerSessions.forEach(ProviderSession::invokeSession); return cancelTransport; } @@ -497,7 +540,8 @@ public final class CredentialManagerService callingUid, callback, request, - constructCallingAppInfo(callingPackage, userId)); + constructCallingAppInfo(callingPackage, userId), + CancellationSignal.fromTransport(cancelTransport)); // Initiate all provider sessions // TODO: Determine if provider needs to have clear capability in their manifest @@ -518,8 +562,7 @@ public final class CredentialManagerService } // Iterate over all provider sessions and invoke the request - providerSessions.forEach( - ProviderSession::invokeSession); + providerSessions.forEach(ProviderSession::invokeSession); return cancelTransport; } diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index e3a27ecebd31..e732c23ca2d9 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -25,6 +25,7 @@ import android.credentials.GetCredentialResponse; import android.credentials.IGetCredentialCallback; import android.credentials.ui.ProviderData; import android.credentials.ui.RequestInfo; +import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialProviderInfo; @@ -43,8 +44,9 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest public GetRequestSession(Context context, int userId, int callingUid, IGetCredentialCallback callback, GetCredentialRequest request, - CallingAppInfo callingAppInfo) { - super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET, callingAppInfo); + CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) { + super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET, + callingAppInfo, cancellationSignal); } /** @@ -102,6 +104,12 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest } private void respondToClientWithResponseAndFinish(GetCredentialResponse response) { + if (isSessionCancelled()) { + // TODO: Differentiate btw cancelled and false + logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false); + finishSession(/*propagateCancellation=*/true); + return; + } try { mClientCallback.onResponse(response); logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ true); @@ -109,18 +117,22 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage()); logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false); } - finishSession(); + finishSession(/*propagateCancellation=*/false); } private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { + if (isSessionCancelled()) { + logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false); + finishSession(/*propagateCancellation=*/true); + return; + } try { mClientCallback.onError(errorType, errorMsg); } catch (RemoteException e) { Log.i(TAG, "Issue while responding to client with error : " + e.getMessage()); - } logApiCalled(RequestType.GET_CREDENTIALS, /* isSuccessful */ false); - finishSession(); + finishSession(/*propagateCancellation=*/false); } @Override diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index b1126499d56f..b20f0cd0f906 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -118,8 +118,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS @Override protected void invokeSession() { - this.mRemoteCredentialService.onClearCredentialState( - this.getProviderRequest(), - /*callback=*/this); + if (mRemoteCredentialService != null) { + mRemoteCredentialService.onClearCredentialState(mProviderRequest, this); + } } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index cc5a8ab5e729..ade40ad064ee 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -51,6 +51,8 @@ public final class ProviderCreateSession extends ProviderSession< // Key to be used as an entry key for a save entry private static final String SAVE_ENTRY_KEY = "save_entry_key"; + // Key to be used as an entry key for a remote entry + private static final String REMOTE_ENTRY_KEY = "remote_entry_key"; @NonNull private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>(); @@ -199,9 +201,9 @@ public final class ProviderCreateSession extends ProviderSession< @Override protected void invokeSession() { - this.mRemoteCredentialService.onCreateCredential( - this.getProviderRequest(), - /*callback=*/this); + if (mRemoteCredentialService != null) { + mRemoteCredentialService.onCreateCredential(mProviderRequest, this); + } } private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) { diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index dec34325bbdd..3ccead106075 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -59,14 +59,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential implements RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> { private static final String TAG = "ProviderGetSession"; - - // Key to be used as an entry key for a credential entry - private static final String CREDENTIAL_ENTRY_KEY = "credential_key"; - // Key to be used as the entry key for an action entry private static final String ACTION_ENTRY_KEY = "action_key"; // Key to be used as the entry key for the authentication entry private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key"; + // Key to be used as an entry key for a remote entry + private static final String REMOTE_ENTRY_KEY = "remote_entry_key"; + // Key to be used as an entry key for a credential entry + private static final String CREDENTIAL_ENTRY_KEY = "credential_key"; @NonNull private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>(); @@ -101,23 +101,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential return null; } - private static BeginGetCredentialRequest constructQueryPhaseRequest( - android.credentials.GetCredentialRequest filteredRequest, - CallingAppInfo callingAppInfo - ) { - return new BeginGetCredentialRequest.Builder(callingAppInfo) - .setBeginGetCredentialOptions( - filteredRequest.getCredentialOptions().stream().map( - option -> { - return new BeginGetCredentialOption( - option.getType(), - option.getCandidateQueryData()); - }).collect(Collectors.toList())) - .build(); - } - @Nullable - private static android.credentials.GetCredentialRequest filterOptions( + protected static android.credentials.GetCredentialRequest filterOptions( List<String> providerCapabilities, android.credentials.GetCredentialRequest clientRequest ) { @@ -142,6 +127,21 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential return null; } + private static BeginGetCredentialRequest constructQueryPhaseRequest( + android.credentials.GetCredentialRequest filteredRequest, + CallingAppInfo callingAppInfo + ) { + return new BeginGetCredentialRequest.Builder(callingAppInfo) + .setBeginGetCredentialOptions( + filteredRequest.getCredentialOptions().stream().map( + option -> { + return new BeginGetCredentialOption( + option.getType(), + option.getCandidateQueryData()); + }).collect(Collectors.toList())) + .build(); + } + public ProviderGetSession(Context context, CredentialProviderInfo info, ProviderInternalCallback<GetCredentialResponse> callbacks, @@ -232,9 +232,9 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential @Override protected void invokeSession() { - this.mRemoteCredentialService.onBeginGetCredential( - this.getProviderRequest(), - /*callback=*/this); + if (mRemoteCredentialService != null) { + mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this); + } } @Override // Call from request session to data to be shown on the UI @@ -379,6 +379,28 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential invokeCallbackOnInternalInvalidState(); } + @Nullable + protected GetCredentialException maybeGetPendingIntentException( + ProviderPendingIntentResponse pendingIntentResponse) { + if (pendingIntentResponse == null) { + Log.i(TAG, "pendingIntentResponse is null"); + return null; + } + if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { + GetCredentialException exception = PendingIntentResultHandler + .extractGetCredentialException(pendingIntentResponse.getResultData()); + if (exception != null) { + Log.i(TAG, "Pending intent contains provider exception"); + return exception; + } + } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { + return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED); + } else { + return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL); + } + return null; + } + private void onAuthenticationEntrySelected( @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) { //TODO: Other provider intent statuses @@ -431,28 +453,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential updateStatusAndInvokeCallback(Status.NO_CREDENTIALS); } - @Nullable - private GetCredentialException maybeGetPendingIntentException( - ProviderPendingIntentResponse pendingIntentResponse) { - if (pendingIntentResponse == null) { - Log.i(TAG, "pendingIntentResponse is null"); - return null; - } - if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { - GetCredentialException exception = PendingIntentResultHandler - .extractGetCredentialException(pendingIntentResponse.getResultData()); - if (exception != null) { - Log.i(TAG, "Pending intent contains provider exception"); - return exception; - } - } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { - return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED); - } else { - return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL); - } - return null; - } - /** * When an invalid state occurs, e.g. entry mismatch or no response from provider, * we send back a TYPE_UNKNOWN error as to the developer. diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java new file mode 100644 index 000000000000..461f447fcc4a --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -0,0 +1,261 @@ +/* + * 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.credentials; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.Context; +import android.content.Intent; +import android.credentials.CredentialOption; +import android.credentials.GetCredentialException; +import android.credentials.GetCredentialRequest; +import android.credentials.GetCredentialResponse; +import android.credentials.ui.Entry; +import android.credentials.ui.GetCredentialProviderData; +import android.credentials.ui.ProviderData; +import android.credentials.ui.ProviderPendingIntentResponse; +import android.service.credentials.CallingAppInfo; +import android.service.credentials.CredentialEntry; +import android.service.credentials.CredentialProviderService; +import android.telecom.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Central provider session that utilizes {@link CredentialDescriptionRegistry} and therefor is able + * to bypass having to use a {@link RemoteCredentialService}. + * + * @hide + */ +public class ProviderRegistryGetSession extends ProviderSession<GetCredentialRequest, + Set<CredentialDescriptionRegistry.FilterResult>> { + + private static final String TAG = "ProviderRegistryGetSession"; + private static final String CREDENTIAL_ENTRY_KEY = "credential_key"; + + /** Creates a new provider session to be used by the request session. */ + @Nullable + public static ProviderRegistryGetSession createNewSession( + @NonNull Context context, + @UserIdInt int userId, + @NonNull GetRequestSession getRequestSession, + @NonNull String credentialProviderPackageName, + @NonNull List<String> requestOptions) { + return new ProviderRegistryGetSession( + context, + userId, + getRequestSession, + getRequestSession.mClientRequest, + getRequestSession.mClientAppInfo, + credentialProviderPackageName, + requestOptions); + } + + @NonNull + private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>(); + @NonNull + private final CredentialDescriptionRegistry mCredentialDescriptionRegistry; + @NonNull + private final CallingAppInfo mCallingAppInfo; + @NonNull + private final String mCredentialProviderPackageName; + @NonNull + private final GetRequestSession mGetRequestSession; + @NonNull + private final List<String> mRequestOptions; + private List<CredentialEntry> mCredentialEntries; + + protected ProviderRegistryGetSession(@NonNull Context context, + @NonNull int userId, + @NonNull GetRequestSession session, + @NonNull GetCredentialRequest request, + @NonNull CallingAppInfo callingAppInfo, + @NonNull String servicePackageName, + @NonNull List<String> requestOptions) { + super(context, null, request, session, userId, null); + mGetRequestSession = session; + mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId); + mCallingAppInfo = callingAppInfo; + mCredentialProviderPackageName = servicePackageName; + mRequestOptions = requestOptions; + } + + private List<Entry> prepareUiCredentialEntries( + @NonNull List<CredentialEntry> credentialEntries) { + Log.i(TAG, "in prepareUiProviderDataWithCredentials"); + List<Entry> credentialUiEntries = new ArrayList<>(); + + // Populate the credential entries + for (CredentialEntry credentialEntry : credentialEntries) { + String entryId = generateEntryId(); + mUiCredentialEntries.put(entryId, credentialEntry); + Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId); + credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId, + credentialEntry.getSlice(), + setUpFillInIntent(credentialEntry.getType()))); + } + return credentialUiEntries; + } + + private Intent setUpFillInIntent(String type) { + Intent intent = new Intent(); + for (CredentialOption option : mProviderRequest.getCredentialOptions()) { + if (option.getType().equals(type)) { + intent.putExtra( + CredentialProviderService + .EXTRA_GET_CREDENTIAL_REQUEST, + new android.service.credentials.GetCredentialRequest( + mCallingAppInfo, option)); + return intent; + } + } + return intent; + } + + @Override + protected ProviderData prepareUiData() { + Log.i(TAG, "In prepareUiData"); + if (!ProviderSession.isUiInvokingStatus(getStatus())) { + Log.i(TAG, "In prepareUiData - provider does not want to show UI: " + + mComponentName.flattenToString()); + return null; + } + if (mProviderResponse == null) { + Log.i(TAG, "In prepareUiData response null"); + throw new IllegalStateException("Response must be in completion mode"); + } + return new GetCredentialProviderData.Builder( + mComponentName.flattenToString()).setActionChips(null) + .setCredentialEntries(prepareUiCredentialEntries( + mProviderResponse.stream().flatMap((Function<CredentialDescriptionRegistry + .FilterResult, + Stream<CredentialEntry>>) filterResult -> + filterResult.mCredentialEntries.stream()) + .collect(Collectors.toList()))) + .build(); + } + + @Override // Selection call from the request provider + protected void onUiEntrySelected(String entryType, String entryKey, + ProviderPendingIntentResponse providerPendingIntentResponse) { + switch (entryType) { + case CREDENTIAL_ENTRY_KEY: + CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey); + if (credentialEntry == null) { + Log.i(TAG, "Unexpected credential entry key"); + return; + } + onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse); + break; + default: + Log.i(TAG, "Unsupported entry type selected"); + } + } + + private void onCredentialEntrySelected(CredentialEntry credentialEntry, + ProviderPendingIntentResponse providerPendingIntentResponse) { + if (!mCredentialEntries.contains(credentialEntry)) { + invokeCallbackWithError("", + ""); + } + + if (providerPendingIntentResponse != null) { + // Check if pending intent has an error + GetCredentialException exception = maybeGetPendingIntentException( + providerPendingIntentResponse); + if (exception != null) { + invokeCallbackWithError(exception.getType(), + exception.getMessage()); + return; + } + + // Check if pending intent has a credential + GetCredentialResponse getCredentialResponse = PendingIntentResultHandler + .extractGetCredentialResponse( + providerPendingIntentResponse.getResultData()); + if (getCredentialResponse != null) { + if (mCallbacks != null) { + mCallbacks.onFinalResponseReceived(mComponentName, + getCredentialResponse); + } + return; + } + + Log.i(TAG, "Pending intent response contains no credential, or error"); + } + Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result"); + } + + @Override + public void onProviderResponseSuccess( + @Nullable Set<CredentialDescriptionRegistry.FilterResult> response) { + // No need to do anything since this class does not rely on a remote service. + } + + @Override + public void onProviderResponseFailure(int internalErrorCode, @Nullable Exception e) { + // No need to do anything since this class does not rely on a remote service. + } + + @Override + public void onProviderServiceDied(RemoteCredentialService service) { + // No need to do anything since this class does not rely on a remote service. + } + + @Override + protected void invokeSession() { + mProviderResponse = mCredentialDescriptionRegistry + .getFilteredResultForProvider(mCredentialProviderPackageName, + mRequestOptions); + mCredentialEntries = mProviderResponse.stream().flatMap( + (Function<CredentialDescriptionRegistry.FilterResult, + Stream<CredentialEntry>>) filterResult + -> filterResult.mCredentialEntries.stream()) + .collect(Collectors.toList()); + setStatus(Status.CREDENTIALS_RECEIVED); + } + + @Nullable + protected GetCredentialException maybeGetPendingIntentException( + ProviderPendingIntentResponse pendingIntentResponse) { + if (pendingIntentResponse == null) { + android.util.Log.i(TAG, "pendingIntentResponse is null"); + return null; + } + if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { + GetCredentialException exception = PendingIntentResultHandler + .extractGetCredentialException(pendingIntentResponse.getResultData()); + if (exception != null) { + android.util.Log.i(TAG, "Pending intent contains provider exception"); + return exception; + } + } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { + return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED); + } else { + return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL); + } + return null; + } +} diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 8e0d6f86a199..d6f97ff6e04d 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -23,8 +23,11 @@ import android.content.Context; import android.credentials.Credential; import android.credentials.ui.ProviderData; import android.credentials.ui.ProviderPendingIntentResponse; +import android.os.ICancellationSignal; +import android.os.RemoteException; import android.service.credentials.CredentialEntry; import android.service.credentials.CredentialProviderInfo; +import android.util.Log; import android.util.Pair; import java.util.UUID; @@ -38,17 +41,16 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.ProviderCallbacks<R> { private static final String TAG = "ProviderSession"; - // Key to be used as an entry key for a remote entry - protected static final String REMOTE_ENTRY_KEY = "remote_entry_key"; @NonNull protected final Context mContext; @NonNull protected final ComponentName mComponentName; - @NonNull protected final CredentialProviderInfo mProviderInfo; - @NonNull protected final RemoteCredentialService mRemoteCredentialService; + @Nullable protected final CredentialProviderInfo mProviderInfo; + @Nullable protected final RemoteCredentialService mRemoteCredentialService; @NonNull protected final int mUserId; @NonNull protected Status mStatus = Status.NOT_STARTED; - @NonNull protected final ProviderInternalCallback mCallbacks; + @Nullable protected final ProviderInternalCallback mCallbacks; @Nullable protected Credential mFinalCredentialResponse; + @Nullable protected ICancellationSignal mProviderCancellationSignal; @NonNull protected final T mProviderRequest; @Nullable protected R mProviderResponse; @NonNull protected Boolean mProviderResponseSet = false; @@ -109,9 +111,9 @@ public abstract class ProviderSession<T, R> protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info, @NonNull T providerRequest, - @NonNull ProviderInternalCallback callbacks, + @Nullable ProviderInternalCallback callbacks, @NonNull int userId, - @NonNull RemoteCredentialService remoteCredentialService) { + @Nullable RemoteCredentialService remoteCredentialService) { mContext = context; mProviderInfo = info; mProviderRequest = providerRequest; @@ -151,6 +153,18 @@ public abstract class ProviderSession<T, R> return mFinalCredentialResponse; } + /** Propagates cancellation signal to the remote provider service. */ + public void cancelProviderRemoteSession() { + try { + if (mProviderCancellationSignal != null) { + mProviderCancellationSignal.cancel(); + } + setStatus(Status.CANCELED); + } catch (RemoteException e) { + Log.i(TAG, "Issue while cancelling provider session: " + e.getMessage()); + } + } + protected void setStatus(@NonNull Status status) { mStatus = status; } @@ -165,7 +179,7 @@ public abstract class ProviderSession<T, R> return mComponentName; } - @NonNull + @Nullable protected RemoteCredentialService getRemoteCredentialService() { return mRemoteCredentialService; } diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java index 8cad6acebc4a..2dea8bda78d5 100644 --- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java +++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java @@ -110,7 +110,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * @param callback the callback to be used to send back the provider response to the * {@link ProviderGetSession} class that maintains provider state */ - public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request, + public ICancellationSignal onBeginGetCredential(@NonNull BeginGetCredentialRequest request, ProviderCallbacks<BeginGetCredentialResponse> callback) { Log.i(TAG, "In onGetCredentials in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); @@ -149,6 +149,8 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> handleExecutionResponse(result, error, cancellationSink, callback))); + + return cancellationSink.get(); } /** Main entry point to be called for executing a beginCreateCredential call on the remote @@ -157,7 +159,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * @param callback the callback to be used to send back the provider response to the * {@link ProviderCreateSession} class that maintains provider state */ - public void onCreateCredential(@NonNull BeginCreateCredentialRequest request, + public ICancellationSignal onCreateCredential(@NonNull BeginCreateCredentialRequest request, ProviderCallbacks<BeginCreateCredentialResponse> callback) { Log.i(TAG, "In onCreateCredential in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); @@ -196,6 +198,8 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> handleExecutionResponse(result, error, cancellationSink, callback))); + + return cancellationSink.get(); } /** Main entry point to be called for executing a clearCredentialState call on the remote @@ -204,7 +208,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * @param callback the callback to be used to send back the provider response to the * {@link ProviderClearSession} class that maintains provider state */ - public void onClearCredentialState(@NonNull ClearCredentialStateRequest request, + public ICancellationSignal onClearCredentialState(@NonNull ClearCredentialStateRequest request, ProviderCallbacks<Void> callback) { Log.i(TAG, "In onClearCredentialState in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); @@ -243,6 +247,8 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> handleExecutionResponse(result, error, cancellationSink, callback))); + + return cancellationSink.get(); } private <T> void handleExecutionResponse(T result, diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index f92ffe208cf4..9f1bd8f69dde 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -29,6 +29,7 @@ import android.content.Context; import android.credentials.ui.ProviderData; import android.credentials.ui.UserSelectionDialogResult; import android.os.Binder; +import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -83,13 +84,16 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan private final int mCallingUid; @NonNull protected final CallingAppInfo mClientAppInfo; + @NonNull + protected final CancellationSignal mCancellationSignal; protected final Map<String, ProviderSession> mProviders = new HashMap<>(); protected RequestSession(@NonNull Context context, @UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback, @NonNull String requestType, - CallingAppInfo callingAppInfo) { + CallingAppInfo callingAppInfo, + CancellationSignal cancellationSignal) { mContext = context; mUserId = userId; mCallingUid = callingUid; @@ -97,6 +101,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan mClientCallback = clientCallback; mRequestType = requestType; mClientAppInfo = callingAppInfo; + mCancellationSignal = cancellationSignal; mHandler = new Handler(Looper.getMainLooper(), null, true); mRequestId = new Binder(); mCredentialManagerUi = new CredentialManagerUi(mContext, @@ -112,6 +117,10 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan @Override // from CredentialManagerUiCallbacks public void onUiSelection(UserSelectionDialogResult selection) { + if (isSessionCancelled()) { + finishSession(/*propagateCancellation=*/true); + return; + } String providerId = selection.getProviderId(); Log.i(TAG, "onUiSelection, providerId: " + providerId); ProviderSession providerSession = mProviders.get(providerId); @@ -127,18 +136,19 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan @Override // from CredentialManagerUiCallbacks public void onUiCancellation(boolean isUserCancellation) { Log.i(TAG, "Ui canceled. Canceled by user: " + isUserCancellation); + if (isSessionCancelled()) { + finishSession(/*propagateCancellation=*/true); + return; + } // User canceled the activity - finishSession(); + finishSession(/*propagateCancellation=*/false); } - protected void finishSession() { + protected void finishSession(boolean propagateCancellation) { Log.i(TAG, "finishing session"); - clearProviderSessions(); - } - - protected void clearProviderSessions() { - Log.i(TAG, "Clearing sessions"); - //TODO: Implement + if (propagateCancellation) { + mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession); + } mProviders.clear(); } @@ -178,6 +188,10 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan isSuccessful ? METRICS_API_STATUS_SUCCESS : METRICS_API_STATUS_FAILURE); } + protected boolean isSessionCancelled() { + return mCancellationSignal.isCanceled(); + } + /** * Returns true if at least one provider is ready for UI invocation, and no * provider is pending a response. @@ -197,6 +211,11 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan Log.i(TAG, "In getProviderDataAndInitiateUi"); Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size()); + if (isSessionCancelled()) { + finishSession(/*propagateCancellation=*/true); + return; + } + ArrayList<ProviderData> providerDataList = new ArrayList<>(); for (ProviderSession session : mProviders.values()) { Log.i(TAG, "preparing data for : " + session.getComponentName()); @@ -208,7 +227,11 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan } if (!providerDataList.isEmpty()) { Log.i(TAG, "provider list not empty about to initiate ui"); - launchUiWithProviderData(providerDataList); + if (isSessionCancelled()) { + Log.i(TAG, "In getProviderDataAndInitiateUi but session has been cancelled"); + } else { + launchUiWithProviderData(providerDataList); + } } } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f412dd34d91e..cae6c39a1a10 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -38,6 +38,7 @@ import android.app.INotificationManager; import android.app.SystemServiceRegistry; import android.app.admin.DevicePolicySafetyChecker; import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -2902,6 +2903,27 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } + if (isWatch) { + t.traceBegin("StartWearService"); + String wearServiceComponentNameString = + context.getString(R.string.config_wearServiceComponent); + + if (!TextUtils.isEmpty(wearServiceComponentNameString)) { + ComponentName wearServiceComponentName = ComponentName.unflattenFromString( + wearServiceComponentNameString); + + if (wearServiceComponentName != null) { + Intent intent = new Intent(); + intent.setComponent(wearServiceComponentName); + intent.addFlags(Intent.FLAG_DIRECT_BOOT_AUTO); + context.startServiceAsUser(intent, UserHandle.SYSTEM); + } else { + Slog.d(TAG, "Null wear service component name."); + } + } + t.traceEnd(); + } + // Enable airplane mode in safe mode. setAirplaneMode() cannot be called // earlier as it sends broadcasts to other services. // TODO: This may actually be too late if radio firmware already started leaking diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt index 02da25de20f1..59551a3bbce3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt @@ -196,7 +196,9 @@ class UidPermissionPolicy : SchemePolicy() { val changedPermissionNames = IndexedSet<String>() trimPermissions(packageName, changedPermissionNames) - trimPermissionStates(appId) + if (appId in newState.systemState.appIds) { + trimPermissionStates(appId) + } changedPermissionNames.forEachIndexed { _, permissionName -> evaluatePermissionStateForAllPackages(permissionName, null) } @@ -1001,10 +1003,7 @@ class UidPermissionPolicy : SchemePolicy() { permissionName: String ): Boolean? { val permissionAllowlist = newState.systemState.permissionAllowlist - val apexModuleName = permissionAllowlist.apexPrivilegedAppAllowlists - .firstNotNullOfOrNullIndexed { _, apexModuleName, apexAllowlist -> - if (packageState.apexModuleName in apexAllowlist) apexModuleName else null - } + val apexModuleName = packageState.apexModuleName val packageName = packageState.packageName return when { packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState( @@ -1071,7 +1070,7 @@ class UidPermissionPolicy : SchemePolicy() { state: AccessState = newState, action: (PackageState) -> Unit ) { - val packageNames = state.systemState.appIds[appId] + val packageNames = state.systemState.appIds[appId]!! packageNames.forEachIndexed { _, packageName -> val packageState = state.systemState.packageStates[packageName]!! if (packageState.androidPackage != null) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java index 8979585d9235..38cf6341e798 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java @@ -75,8 +75,8 @@ public final class UserVisibilityMediatorMUPANDTest assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID); // Make sure another user cannot be started on default display - int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE, - DEFAULT_DISPLAY); + int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId, + BG_VISIBLE, DEFAULT_DISPLAY); assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE, "when user (%d) is starting on default display after it was started by user %d", otherUserId, visibleBgUserId); @@ -119,8 +119,8 @@ public final class UserVisibilityMediatorMUPANDTest assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID); // Make sure another user cannot be started on default display - int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE, - DEFAULT_DISPLAY); + int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId, + BG_VISIBLE, DEFAULT_DISPLAY); assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE, "when user (%d) is starting on default display after it was started by user %d", otherUserId, visibleBgUserId); @@ -128,6 +128,7 @@ public final class UserVisibilityMediatorMUPANDTest listener.verify(); } + /* TODO: re-add @Test public void @@ -226,4 +227,5 @@ public final class UserVisibilityMediatorMUPANDTest listener.verify(); } + */ } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java index 566084adfae3..5176d684ace6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java @@ -44,6 +44,7 @@ import android.text.TextUtils; import android.util.IntArray; import android.util.Log; +import com.android.internal.util.Preconditions; import com.android.server.ExtendedMockitoTestCase; import org.junit.Before; @@ -148,12 +149,6 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { } @Test - public final void testAssignUserToDisplayOnStart_invalidUserStartMode() { - assertThrows(IllegalArgumentException.class, () -> mMediator - .assignUserToDisplayOnStart(USER_ID, USER_ID, 666, DEFAULT_DISPLAY)); - } - - @Test public final void testStartFgUser_onSecondaryDisplay() throws Exception { AsyncUserVisibilityListener listener = addListenerForNoEvents(); @@ -288,7 +283,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG_VISIBLE, DEFAULT_DISPLAY); - assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE); expectUserIsNotVisibleAtAll(PROFILE_USER_ID); expectNoDisplayAssignedToUser(PROFILE_USER_ID); @@ -304,14 +299,14 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG_VISIBLE, DEFAULT_DISPLAY); - assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE); expectUserIsNotVisibleAtAll(PROFILE_USER_ID); expectNoDisplayAssignedToUser(PROFILE_USER_ID); expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY); - assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); + assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); listener.verify(); } @@ -337,41 +332,6 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { } @Test - public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted() - throws Exception { - AsyncUserVisibilityListener listener = addListenerForNoEvents(); - - int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG, - DEFAULT_DISPLAY); - assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE); - - expectUserIsNotVisibleAtAll(PROFILE_USER_ID); - expectNoDisplayAssignedToUser(PROFILE_USER_ID); - - listener.verify(); - } - - @Test - public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg() - throws Exception { - AsyncUserVisibilityListener listener = addListenerForNoEvents(); - startBackgroundUser(PARENT_USER_ID); - - int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG, - DEFAULT_DISPLAY); - assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE); - - expectUserIsNotVisibleAtAll(PROFILE_USER_ID); - - expectNoDisplayAssignedToUser(PROFILE_USER_ID); - expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY); - - assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); - - listener.verify(); - } - - @Test public final void testStartBgProfile_onSecondaryDisplay() throws Exception { AsyncUserVisibilityListener listener = addListenerForNoEvents(); @@ -525,6 +485,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { * se. */ protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) { + Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY, + "must pass a secondary display, not %d", displayId); Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")"); int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId); if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java index f0840633e054..49c6a8854c49 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java @@ -108,6 +108,34 @@ abstract class UserVisibilityMediatorVisibleBackgroundUserTestCase } @Test + public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser() + throws Exception { + AsyncUserVisibilityListener listener = addListenerForEvents( + onInvisible(INITIAL_CURRENT_USER_ID), + onVisible(PARENT_USER_ID), + onVisible(PROFILE_USER_ID)); + startForegroundUser(PARENT_USER_ID); + + int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, + BG_VISIBLE, DEFAULT_DISPLAY); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE); + expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY); + + expectUserIsVisible(PROFILE_USER_ID); + expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY); + expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); + expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY); + expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID); + + expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY); + expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID); + + assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); + + listener.verify(); + } + + @Test public final void testStartFgUser_onInvalidDisplay() throws Exception { AsyncUserVisibilityListener listener = addListenerForNoEvents(); @@ -240,83 +268,14 @@ abstract class UserVisibilityMediatorVisibleBackgroundUserTestCase } @Test - public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser() - throws Exception { - AsyncUserVisibilityListener listener = addListenerForEvents( - onInvisible(INITIAL_CURRENT_USER_ID), - onVisible(PARENT_USER_ID), - onVisible(PROFILE_USER_ID)); - startForegroundUser(PARENT_USER_ID); - - int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, - BG_VISIBLE, DEFAULT_DISPLAY); - assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE); - expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY); - - expectUserIsVisible(PROFILE_USER_ID); - expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY); - expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); - expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY); - expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID); - - expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY); - expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID); - - assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); - - listener.verify(); - } - - @Test public final void - testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnAnotherDisplay() - throws Exception { + testStartVisibleBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay() + throws Exception { AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID)); startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID); int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG_VISIBLE, DEFAULT_DISPLAY); - assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); - - expectUserIsNotVisibleAtAll(PROFILE_USER_ID); - expectNoDisplayAssignedToUser(PROFILE_USER_ID); - expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID); - - assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); - - listener.verify(); - } - - // Not supported - profiles can only be started on default display - @Test - public final void - testStartVisibleBgProfile_onSecondaryDisplay_whenParentIsStartedVisibleOnThatDisplay() - throws Exception { - AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID)); - startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID); - - int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, - BG_VISIBLE, DEFAULT_DISPLAY); - assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); - - expectUserIsNotVisibleAtAll(PROFILE_USER_ID); - expectNoDisplayAssignedToUser(PROFILE_USER_ID); - expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID); - - assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); - - listener.verify(); - } - - @Test - public final void - testStartProfile_onDefaultDisplay_whenParentIsStartedVisibleOnSecondaryDisplay() - throws Exception { - AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID)); - startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID); - - int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG, - DEFAULT_DISPLAY); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE); expectUserIsNotVisibleAtAll(PROFILE_USER_ID); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 99f7905a9f70..e605a317f096 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.content.ComponentName; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; @@ -364,6 +365,16 @@ public class FingerprintAuthenticationClientTest { showHideOverlay(c -> c.onLockoutPermanent()); } + @Test + public void testPowerPressForwardsErrorMessage() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(); + + client.onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR, + BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED); + + verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), + eq(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED), eq(0)); + } private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block) throws RemoteException { final FingerprintAuthenticationClient client = createClient(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index 26524d7df7c3..a40d3fe3a3d6 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -16,8 +16,6 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED; - import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -29,6 +27,7 @@ import static org.mockito.Mockito.same; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.PointerContext; @@ -276,11 +275,12 @@ public class FingerprintEnrollClientTest { @Test public void testPowerPressForwardsAcquireMessage() throws RemoteException { final FingerprintEnrollClient client = createClient(); - client.start(mCallback); - client.onPowerPressed(); + + client.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR, + BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED); verify(mClientMonitorCallbackConverter).onAcquired(anyInt(), - eq(FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt()); + eq(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED), eq(0)); } private void showHideOverlay(Consumer<FingerprintEnrollClient> block) diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 27d912be1c0d..0eff0da0ed3e 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -2402,6 +2402,74 @@ public class DisplayModeDirectorTest { eq(lightSensorTwo), anyInt(), any(Handler.class)); } + @Test + public void testAuthenticationPossibleSetsPhysicalRateRangesToMax() throws RemoteException { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + // don't call director.start(createMockSensorManager()); + // DisplayObserver will reset mSupportedModesByDisplay + director.onBootCompleted(); + ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor = + ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class); + verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture()); + + captor.getValue().onAuthenticationPossible(DISPLAY_ID, true); + + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE); + assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90); + } + + @Test + public void testAuthenticationPossibleUnsetsVote() throws RemoteException { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + director.start(createMockSensorManager()); + director.onBootCompleted(); + ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor = + ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class); + verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture()); + captor.getValue().onAuthenticationPossible(DISPLAY_ID, true); + captor.getValue().onAuthenticationPossible(DISPLAY_ID, false); + + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE); + assertNull(vote); + } + + @Test + public void testUdfpsRequestSetsPhysicalRateRangesToMax() throws RemoteException { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + // don't call director.start(createMockSensorManager()); + // DisplayObserver will reset mSupportedModesByDisplay + director.onBootCompleted(); + ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor = + ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class); + verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture()); + + captor.getValue().onRequestEnabled(DISPLAY_ID); + + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS); + assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90); + } + + @Test + public void testUdfpsRequestUnsetsUnsetsVote() throws RemoteException { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0); + director.start(createMockSensorManager()); + director.onBootCompleted(); + ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor = + ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class); + verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture()); + captor.getValue().onRequestEnabled(DISPLAY_ID); + captor.getValue().onRequestDisabled(DISPLAY_ID); + + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS); + assertNull(vote); + } + private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) { return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); } diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index ac1dca965310..488c533f6c63 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -836,9 +836,9 @@ public class LogicalDisplayMapperTest { assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); assertEquals(POSITION_UNKNOWN, - mLogicalDisplayMapper.getDisplayLocked(device1).getPositionLocked()); + mLogicalDisplayMapper.getDisplayLocked(device1).getDevicePositionLocked()); assertEquals(POSITION_REAR, - mLogicalDisplayMapper.getDisplayLocked(device2).getPositionLocked()); + mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked()); } ///////////////// diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index 1d70fc61c937..d28050d29b4a 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -26,12 +26,15 @@ import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; import android.graphics.Point; +import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; import androidx.test.filters.SmallTest; +import com.android.server.display.layout.Layout; + import org.junit.Before; import org.junit.Test; @@ -47,6 +50,7 @@ public class LogicalDisplayTest { private LogicalDisplay mLogicalDisplay; private DisplayDevice mDisplayDevice; + private DisplayDeviceRepository mDeviceRepo; private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo(); @Before @@ -66,7 +70,7 @@ public class LogicalDisplayTest { // Disable binder caches in this process. PropertyInvalidatedCache.disableForTestMode(); - DisplayDeviceRepository repo = new DisplayDeviceRepository( + mDeviceRepo = new DisplayDeviceRepository( new DisplayManagerService.SyncRoot(), new PersistentDataStore(new PersistentDataStore.Injector() { @Override @@ -82,8 +86,8 @@ public class LogicalDisplayTest { @Override public void finishWrite(OutputStream os, boolean success) {} })); - repo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); - mLogicalDisplay.updateLocked(repo); + mDeviceRepo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + mLogicalDisplay.updateLocked(mDeviceRepo); } @Test @@ -137,4 +141,29 @@ public class LogicalDisplayTest { verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); reset(t); } + + @Test + public void testRearDisplaysArePresentationDisplaysThatDestroyContentOnRemoval() { + // Assert that the display isn't a presentation display by default, with a default remove + // mode + assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags); + assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY, + mLogicalDisplay.getDisplayInfoLocked().removeMode); + + // Update position and test to see that it's been updated to a rear, presentation display + // that destroys content on removal + mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_REAR); + mLogicalDisplay.updateLocked(mDeviceRepo); + assertEquals(Display.FLAG_REAR | Display.FLAG_PRESENTATION, + mLogicalDisplay.getDisplayInfoLocked().flags); + assertEquals(Display.REMOVE_MODE_DESTROY_CONTENT, + mLogicalDisplay.getDisplayInfoLocked().removeMode); + + // And then check the unsetting the position resets both + mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_UNKNOWN); + mLogicalDisplay.updateLocked(mDeviceRepo); + assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags); + assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY, + mLogicalDisplay.getDisplayInfoLocked().removeMode); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index d4ab794f55dc..ebd63a0a2ad3 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -47,7 +47,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public final class DisplayBrightnessControllerTest { private static final int DISPLAY_ID = 1; - private static final float DEFAULT_BRIGHTNESS = 0.4f; + private static final float DEFAULT_BRIGHTNESS = 0.15f; @Mock private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector; @@ -70,11 +70,18 @@ public final class DisplayBrightnessControllerTest { return mDisplayBrightnessStrategySelector; } }; + when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN); mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector, DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable); } @Test + public void testIfFirstScreenBrightnessIsDefault() { + assertEquals(mDisplayBrightnessController.getCurrentBrightness(), DEFAULT_BRIGHTNESS, + 0.0f); + } + + @Test public void testUpdateBrightness() { DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class); DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java index cff3bf8f80ad..b27ba88d4ccc 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java @@ -38,7 +38,6 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,8 +50,8 @@ public class BatteryStatsUserLifecycleTests { private static final long POLL_INTERVAL_MS = 500; private static final long USER_REMOVE_TIMEOUT_MS = 5_000; - private static final long STOP_USER_TIMEOUT_MS = 10_000; - private static final long USER_UIDS_REMOVE_TIMEOUT_MS = 15_000; + private static final long STOP_USER_TIMEOUT_MS = 20_000; + private static final long USER_UIDS_REMOVE_TIMEOUT_MS = 20_000; private static final long BATTERYSTATS_POLLING_TIMEOUT_MS = 5_000; private static final String CPU_DATA_TAG = "cpu"; @@ -79,26 +78,29 @@ public class BatteryStatsUserLifecycleTests { batteryOnScreenOff(); } - @Ignore("b/244349060") @Test public void testNoCpuDataForRemovedUser() throws Exception { mIam.startUserInBackground(mTestUserId); waitUntilTrue("No uids for started user " + mTestUserId, () -> getNumberOfUidsInBatteryStats() > 0, BATTERYSTATS_POLLING_TIMEOUT_MS); + final boolean[] userStopped = new boolean[1]; CountDownLatch stopUserLatch = new CountDownLatch(1); mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() { @Override public void userStopped(int userId) throws RemoteException { + userStopped[0] = true; stopUserLatch.countDown(); } @Override public void userStopAborted(int userId) throws RemoteException { + stopUserLatch.countDown(); } }); - assertTrue("User " + mTestUserId + " could not be stopped", + assertTrue("User " + mTestUserId + " could not be stopped in " + STOP_USER_TIMEOUT_MS, stopUserLatch.await(STOP_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertTrue("User " + mTestUserId + " could not be stopped", userStopped[0]); mUm.removeUser(mTestUserId); waitUntilTrue("Unable to remove user " + mTestUserId, () -> { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 56d59b41133a..e5fe32a959e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -3177,7 +3177,7 @@ public class ActivityRecordTests extends WindowTestsBase { public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer( + mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer( mImeWindow, null, null); mImeWindow.getControllableInsetProvider().setServerVisible(true); @@ -3222,7 +3222,7 @@ public class ActivityRecordTests extends WindowTestsBase { final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); - mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer( + mDisplayContent.getInsetsStateController().getImeSourceProvider().setWindowContainer( mImeWindow, null, null); mImeWindow.getControllableInsetProvider().setServerVisible(true); @@ -3288,7 +3288,7 @@ public class ActivityRecordTests extends WindowTestsBase { makeWindowVisibleAndDrawn(app1, app2); final InsetsStateController controller = mDisplayContent.getInsetsStateController(); - controller.getSourceProvider(ID_IME).setWindowContainer( + controller.getImeSourceProvider().setWindowContainer( ime, null, null); ime.getControllableInsetProvider().setServerVisible(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 45cf530eddde..6656f4c79022 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -163,19 +163,20 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { InsetsStateController controller = mDisplayContent.getInsetsStateController(); controller.onPostLayout(); - InsetsSourceProvider statusBarProvider = controller.getSourceProvider(ITYPE_STATUS_BAR); + InsetsSourceProvider statusBarProvider = controller.peekSourceProvider(ITYPE_STATUS_BAR); assertEquals(new Rect(0, 0, 500, 100), statusBarProvider.getSource().getFrame()); assertEquals(Insets.of(0, 100, 0, 0), statusBarProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), false /* ignoreVisibility */)); - InsetsSourceProvider topGesturesProvider = controller.getSourceProvider(ITYPE_TOP_GESTURES); + InsetsSourceProvider topGesturesProvider = controller.peekSourceProvider( + ITYPE_TOP_GESTURES); assertEquals(new Rect(0, 0, 500, 100), topGesturesProvider.getSource().getFrame()); assertEquals(Insets.of(0, 100, 0, 0), topGesturesProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), false /* ignoreVisibility */)); - InsetsSourceProvider navigationBarProvider = controller.getSourceProvider( + InsetsSourceProvider navigationBarProvider = controller.peekSourceProvider( ITYPE_NAVIGATION_BAR); assertNotEquals(new Rect(0, 0, 500, 100), navigationBarProvider.getSource().getFrame()); } @@ -194,7 +195,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mDisplayContent.getInsetsStateController().onPostLayout(); InsetsSourceProvider provider = - mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR); + mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_STATUS_BAR); // In the new flexible insets setup, the insets frame should always respect the window // layout result. assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame()); 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 dba299566f84..1a126cfa5c2c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -280,8 +280,7 @@ public class InsetsPolicyTest extends WindowTestsBase { assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState() .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars())); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, - true /* isGestureOnSystemBar */); + policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -308,11 +307,11 @@ public class InsetsPolicyTest extends WindowTestsBase { spyOn(policy); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); - assertTrue(policy.isTransient(ITYPE_STATUS_BAR)); - assertFalse(policy.isTransient(ITYPE_NAVIGATION_BAR)); + assertTrue(policy.isTransient(statusBars())); + assertFalse(policy.isTransient(navigationBars())); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -344,7 +343,7 @@ public class InsetsPolicyTest extends WindowTestsBase { spyOn(policy); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); InsetsSourceControl[] controls = @@ -393,13 +392,13 @@ public class InsetsPolicyTest extends WindowTestsBase { spyOn(policy); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(app); policy.updateBarControlTarget(app2); - assertFalse(policy.isTransient(ITYPE_STATUS_BAR)); - assertFalse(policy.isTransient(ITYPE_NAVIGATION_BAR)); + assertFalse(policy.isTransient(statusBars())); + assertFalse(policy.isTransient(navigationBars())); } private WindowState addNavigationBar() { diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 88ecd3fe2cea..74fde65c4dcd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -20,11 +20,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.InsetsSource.ID_IME; -import static android.view.InsetsState.ITYPE_CLIMATE_BAR; -import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; -import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; -import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; @@ -49,6 +46,7 @@ import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; +import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -64,6 +62,15 @@ import org.junit.runner.RunWith; @RunWith(WindowTestRunner.class) public class InsetsStateControllerTest extends WindowTestsBase { + private static final int ID_STATUS_BAR = + InsetsSource.createId(null /* owner */, 0 /* index */, statusBars()); + private static final int ID_NAVIGATION_BAR = + InsetsSource.createId(null /* owner */, 0 /* index */, navigationBars()); + private static final int ID_CLIMATE_BAR = + InsetsSource.createId(null /* owner */, 1 /* index */, statusBars()); + private static final int ID_EXTRA_NAVIGATION_BAR = + InsetsSource.createId(null /* owner */, 1 /* index */, navigationBars()); + @Test public void testStripForDispatch_navBar() { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); @@ -73,14 +80,15 @@ public class InsetsStateControllerTest extends WindowTestsBase { // IME cannot be the IME target. ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); - getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(ime, null, null); assertNull(navBar.getInsetsState().peekSource(ID_IME)); - assertNull(navBar.getInsetsState().peekSource(ITYPE_STATUS_BAR)); + assertNull(navBar.getInsetsState().peekSource(ID_STATUS_BAR)); } @Test @@ -89,14 +97,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); app.setWindowingMode(WINDOWING_MODE_PINNED); - assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR)); - assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR)); + assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); assertNull(app.getInsetsState().peekSource(ID_IME)); } @@ -106,14 +114,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); app.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR)); - assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR)); + assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); } @Test @@ -122,21 +130,22 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); app.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); app.setAlwaysOnTop(true); - assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR)); - assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR)); + assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); } @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_independentSources() { - getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(mImeWindow, null, null); final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); @@ -151,7 +160,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_belowIme() { - getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); app.mAboveInsetsState.getOrCreateSource(ID_IME, ime()) @@ -165,7 +175,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_aboveIme() { - getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); @@ -184,7 +195,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { // Make IME and stay visible during the test. mImeWindow.setHasSurface(true); - getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(mImeWindow, null, null); getController().onImeControlTargetChanged( mDisplayContent.getImeInputTarget().getWindowState()); mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime()); @@ -228,7 +240,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_childWindow_altFocusable() { - getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); @@ -248,7 +261,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testStripForDispatch_childWindow_splitScreen() { - getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); @@ -274,20 +288,21 @@ public class InsetsStateControllerTest extends WindowTestsBase { ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; WindowContainerInsetsSourceProvider statusBarProvider = - getController().getSourceProvider(ITYPE_STATUS_BAR); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()); final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverrideProviders = new SparseArray<>(); imeOverrideProviders.put(TYPE_INPUT_METHOD, ((displayFrames, windowState, rect) -> rect.set(0, 1, 2, 3))); statusBarProvider.setWindowContainer(statusBar, null, imeOverrideProviders); - getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(ime, null, null); statusBar.setControllableInsetProvider(statusBarProvider); statusBar.updateSourceFrame(statusBar.getFrame()); statusBarProvider.onPostLayout(); final InsetsState state = ime.getInsetsState(); - assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_STATUS_BAR).getFrame()); + assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ID_STATUS_BAR).getFrame()); } @Test @@ -297,15 +312,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar"); final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); - getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindowContainer(climateBar, null, - null); - getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindowContainer( - extraNavBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); + getController().getOrCreateSourceProvider(ID_CLIMATE_BAR, statusBars()) + .setWindowContainer(climateBar, null, null); + getController().getOrCreateSourceProvider(ID_EXTRA_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(extraNavBar, null, null); getController().onBarControlTargetChanged(app, null, app, null); InsetsSourceControl[] controls = getController().getControlsForDispatch(app); assertEquals(4, controls.length); @@ -315,8 +329,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testControlRevoked() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); getController().onBarControlTargetChanged(app, null, null, null); assertNotNull(getController().getControlsForDispatch(app)); getController().onBarControlTargetChanged(null, null, null, null); @@ -327,8 +341,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testControlRevoked_animation() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); getController().onBarControlTargetChanged(app, null, null, null); assertNotNull(getController().getControlsForDispatch(app)); statusBar.cancelAnimation(); @@ -340,22 +354,22 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowContainerInsetsSourceProvider provider = getController() - .getSourceProvider(ITYPE_STATUS_BAR); + .getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()); provider.setWindowContainer(statusBar, null, null); final InsetsState rotatedState = new InsetsState(app.getInsetsState(), true /* copySources */); - rotatedState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars()); + rotatedState.getOrCreateSource(ID_STATUS_BAR, statusBars()); spyOn(app.mToken); doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState(); - assertTrue(rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars())); + assertTrue(rotatedState.isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars())); provider.getSource().setVisible(false); - mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR }, + mDisplayContent.getInsetsPolicy().showTransient(statusBars(), true /* isGestureOnSystemBar */); - assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR)); - assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars())); + assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars())); + assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars())); } @Test @@ -364,18 +378,18 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createTestWindow("statusBar"); final WindowState navBar = createTestWindow("navBar"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); - assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); + assertNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNull(statusBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNull(navBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); getController().updateAboveInsetsState(true /* notifyInsetsChange */); - assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); + assertNotNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNull(statusBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNull(navBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); verify(app, atLeastOnce()).notifyInsetsChanged(); } @@ -386,18 +400,18 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createTestWindow("statusBar"); final WindowState navBar = createTestWindow("navBar"); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); - assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNull(app.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); getController().updateAboveInsetsState(true /* notifyInsetsChange */); - assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR)); + assertNotNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNotNull(app.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); verify(app, atLeastOnce()).notifyInsetsChanged(); } @@ -409,19 +423,21 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState statusBar = createTestWindow("statusBar"); final WindowState navBar = createTestWindow("navBar"); - getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null); + final InsetsSourceProvider imeSourceProvider = + getController().getOrCreateSourceProvider(ID_IME, ime()); + imeSourceProvider.setWindowContainer(ime, null, null); waitUntilHandlersIdle(); clearInvocations(mDisplayContent); - getController().getSourceProvider(ID_IME).setClientVisible(true); + imeSourceProvider.setClientVisible(true); waitUntilHandlersIdle(); // The visibility change should trigger a traversal to notify the change. verify(mDisplayContent).notifyInsetsChanged(any()); - getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null, - null); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); + getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) + .setWindowContainer(statusBar, null, null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); getController().updateAboveInsetsState(false /* notifyInsetsChange */); @@ -429,8 +445,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { assertNull(app.mAboveInsetsState.peekSource(ID_IME)); assertNull(statusBar.mAboveInsetsState.peekSource(ID_IME)); assertNull(navBar.mAboveInsetsState.peekSource(ID_IME)); - assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR)); + assertNotNull(ime.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNotNull(ime.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); ime.getParent().positionChildAt(POSITION_TOP, ime, true /* includingParents */); getController().updateAboveInsetsState(true /* notifyInsetsChange */); @@ -439,8 +455,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { assertNotNull(app.mAboveInsetsState.peekSource(ID_IME)); assertNotNull(statusBar.mAboveInsetsState.peekSource(ID_IME)); assertNotNull(navBar.mAboveInsetsState.peekSource(ID_IME)); - assertNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR)); - assertNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(ime.mAboveInsetsState.peekSource(ID_STATUS_BAR)); + assertNull(ime.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); verify(ime, atLeastOnce()).notifyInsetsChanged(); verify(app, atLeastOnce()).notifyInsetsChanged(); @@ -454,7 +470,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, imeToken, "ime"); final WindowState app = createTestWindow("app"); - getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null); + getController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(ime, null, null); ime.getControllableInsetProvider().setServerVisible(true); app.mActivityRecord.setVisibility(true); @@ -489,12 +506,12 @@ public class InsetsStateControllerTest extends WindowTestsBase { @Test public void testDispatchGlobalInsets() { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); - getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null, - null); + getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) + .setWindowContainer(navBar, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); + assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); app.mAttrs.receiveInsetsIgnoringZOrder = true; - assertNotNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR)); + assertNotNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); } @SetupWindows(addWindows = W_INPUT_METHOD) @@ -504,7 +521,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState app2 = createTestWindow("app2"); makeWindowVisible(mImeWindow); - final InsetsSourceProvider imeInsetsProvider = getController().getSourceProvider(ID_IME); + final InsetsSourceProvider imeInsetsProvider = + getController().getOrCreateSourceProvider(ID_IME, ime()); imeInsetsProvider.setWindowContainer(mImeWindow, null, null); imeInsetsProvider.updateSourceFrame(mImeWindow.getFrame()); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 42bbd2d40f41..94193f4a2dd3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -352,6 +352,7 @@ public class SystemServicesTestRule implements TestRule { mAtmService.setWindowManager(mWmService); mWmService.mDisplayEnabled = true; mWmService.mDisplayReady = true; + mAtmService.getTransitionController().mIsWaitingForDisplayEnabled = false; // Set configuration for default display mWmService.getDefaultDisplayContentLocked().reconfigureDisplayLocked(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java index 1e5ec4c4e48c..7d13de84642e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java @@ -170,10 +170,10 @@ public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase { final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); statusBar.getFrame().set(0, 0, 500, 100); mProvider.setWindowContainer(statusBar, null, null); - mProvider.updateControlForFakeTarget(target); + mProvider.updateFakeControlTarget(target); assertNotNull(mProvider.getControl(target)); assertNull(mProvider.getControl(target).getLeash()); - mProvider.updateControlForFakeTarget(null); + mProvider.updateFakeControlTarget(null); assertNull(mProvider.getControl(target)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index c44869b10642..17b44ee65442 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -437,13 +437,15 @@ public class WindowStateTests extends WindowTestsBase { final WindowState app = mAppWindow; statusBar.mHasSurface = true; assertTrue(statusBar.isVisible()); - mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR) + mDisplayContent.getInsetsStateController() + .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars()) .setWindowContainer(statusBar, null /* frameProvider */, null /* imeFrameProvider */); mDisplayContent.getInsetsStateController().onBarControlTargetChanged( app, null /* fakeTopControlling */, app, null /* fakeNavControlling */); app.setRequestedVisibleTypes(0, statusBars()); - mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR) + mDisplayContent.getInsetsStateController() + .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars()) .updateClientVisibility(app); waitUntilHandlersIdle(); assertFalse(statusBar.isVisible()); @@ -1169,8 +1171,8 @@ public class WindowStateTests extends WindowTestsBase { mNotificationShadeWindow.setHasSurface(true); mNotificationShadeWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; assertTrue(mNotificationShadeWindow.canBeImeTarget()); - mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer( - mImeWindow, null, null); + mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(ID_IME, ime()) + .setWindowContainer(mImeWindow, null, null); mDisplayContent.computeImeTarget(true); assertEquals(mNotificationShadeWindow, mDisplayContent.getImeTarget(IME_TARGET_LAYERING)); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index bd63560ee92f..714eb4b3c093 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -39,6 +39,7 @@ import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.view.WindowInsets; import android.window.WindowContext; import androidx.test.filters.SmallTest; @@ -262,8 +263,9 @@ public class WindowTokenTests extends WindowTestsBase { @Test public void testSetInsetsFrozen_notAffectImeWindowState() { // Pre-condition: make the IME window be controlled by IME insets provider. - mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer( - mDisplayContent.mInputMethodWindow, null, null); + mDisplayContent.getInsetsStateController() + .getOrCreateSourceProvider(ID_IME, WindowInsets.Type.ime()) + .setWindowContainer(mDisplayContent.mInputMethodWindow, null, null); // Simulate an app window to be the IME layering target, assume the app window has no // frozen insets state by default. diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 7c86a75ac023..20564d6f3c14 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -951,6 +951,23 @@ public class TelecomManager { public static final String EXTRA_CALL_SOURCE = "android.telecom.extra.CALL_SOURCE"; /** + * Intent action to trigger "switch to managed profile" dialog for call in SystemUI + * + * @hide + */ + public static final String ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG = + "android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG"; + + /** + * Extra specifying the managed profile user id. + * This is used with {@link TelecomManager#ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG} + * + * @hide + */ + public static final String EXTRA_MANAGED_PROFILE_USER_ID = + "android.telecom.extra.MANAGED_PROFILE_USER_ID"; + + /** * Indicating the call is initiated via emergency dialer's shortcut button. * * @hide diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java index 51ecfb0a0be8..836cb53488ef 100644 --- a/telephony/java/android/telephony/CallState.java +++ b/telephony/java/android/telephony/CallState.java @@ -285,12 +285,12 @@ public final class CallState implements Parcelable { /** * Builder of {@link CallState} * - * <p>The example below shows how you might create a new {@code CallState}: + * <p>The example below shows how you might create a new {@code CallState}. A precise call state + * {@link PreciseCallStates} is mandatory to build a CallState. * * <pre><code> * - * CallState = new CallState.Builder() - * .setCallState(3) + * CallState = new CallState.Builder({@link PreciseCallStates}) * .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE}) * .setCallQuality({@link CallQuality}) * .setImsCallSessionId({@link String}) diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c238f2423f12..819b0b2ee713 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -14894,7 +14894,10 @@ public class TelephonyManager { /** * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the - * the country code in ISO-3166-1 alpha-2 format. + * the country code in ISO-3166-1 alpha-2 format. This is the same country code returned by + * {@link #getNetworkCountryIso()}. This might be an empty string when the country code is not + * available. + * * <p class="note"> * Retrieve with {@link android.content.Intent#getStringExtra(String)}. */ @@ -14903,11 +14906,11 @@ public class TelephonyManager { /** * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the - * last known the country code in ISO-3166-1 alpha-2 format. + * last known the country code in ISO-3166-1 alpha-2 format. This might be an empty string when + * the country code was never available. The last known country code persists across reboot. + * * <p class="note"> * Retrieve with {@link android.content.Intent#getStringExtra(String)}. - * - * @hide */ public static final String EXTRA_LAST_KNOWN_NETWORK_COUNTRY = "android.telephony.extra.LAST_KNOWN_NETWORK_COUNTRY"; diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 1ea7fdc982a5..d07edeb971ea 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.telecom.VideoProfile; +import android.telephony.CallState; import android.telephony.emergency.EmergencyNumber; import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting; import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; @@ -78,7 +79,9 @@ public final class ImsCallProfile implements Parcelable { public static final int SERVICE_TYPE_EMERGENCY = 2; /** - * Call type none + * This value is returned if there is no valid IMS call type defined for the call. For example, + * if an ongoing call is circuit-switched and {@link CallState#getImsCallType()} is called, this + * value will be returned. */ public static final int CALL_TYPE_NONE = 0; /** diff --git a/wifi/java/Android.bp b/wifi/java/Android.bp index 225e750923fd..434226d75b28 100644 --- a/wifi/java/Android.bp +++ b/wifi/java/Android.bp @@ -27,7 +27,10 @@ package { filegroup { name: "framework-wifi-non-updatable-sources-internal", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.aidl", + ], path: "src", visibility: ["//visibility:private"], } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.aidl new file mode 100644 index 000000000000..35d5c15a161b --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +parcelable DeviceInfo;
\ No newline at end of file diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java new file mode 100644 index 000000000000..7874b2a22e02 --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/DeviceInfo.java @@ -0,0 +1,299 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * A data class representing a device providing connectivity. + * This class is used in IPC calls between the implementer of {@link SharedConnectivityService} and + * the consumers of {@link com.android.wifitrackerlib}. + * + * @hide + */ +@SystemApi +public final class DeviceInfo implements Parcelable { + + /** + * Device type providing connectivity is unknown. + */ + public static final int DEVICE_TYPE_UNKNOWN = 0; + + /** + * Device providing connectivity is a mobile phone. + */ + public static final int DEVICE_TYPE_PHONE = 1; + + /** + * Device providing connectivity is a tablet. + */ + public static final int DEVICE_TYPE_TABLET = 2; + + /** + * Device providing connectivity is a laptop. + */ + public static final int DEVICE_TYPE_LAPTOP = 3; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + DEVICE_TYPE_UNKNOWN, + DEVICE_TYPE_PHONE, + DEVICE_TYPE_TABLET, + DEVICE_TYPE_LAPTOP + }) + public @interface DeviceType {} + + @DeviceType private final int mDeviceType; + private final String mDeviceName; + private final String mModelName; + private final int mBatteryPercentage; + private final int mConnectionStrength; + + /** + * Builder class for {@link DeviceInfo}. + */ + public static final class Builder { + private int mDeviceType; + private String mDeviceName; + private String mModelName; + private int mBatteryPercentage; + private int mConnectionStrength; + + public Builder() {} + + /** + * Sets the device type that provides connectivity. + * + * @param deviceType Device type as represented by IntDef {@link DeviceType}. + * @return Returns the Builder object. + */ + @NonNull + public Builder setDeviceType(@DeviceType int deviceType) { + mDeviceType = deviceType; + return this; + } + + /** + * Sets the device name of the remote device. + * + * @param deviceName The user configurable device name. + * @return Returns the Builder object. + */ + @NonNull + public Builder setDeviceName(@NonNull String deviceName) { + mDeviceName = deviceName; + return this; + } + + /** + * Sets the model name of the remote device. + * + * @param modelName The OEM configured name for the device model. + * @return Returns the Builder object. + */ + @NonNull + public Builder setModelName(@NonNull String modelName) { + mModelName = modelName; + return this; + } + + /** + * Sets the battery charge percentage of the remote device. + * + * @param batteryPercentage The battery charge percentage in the range 0 to 100. + * @return Returns the Builder object. + */ + @NonNull + public Builder setBatteryPercentage(@IntRange(from = 0, to = 100) int batteryPercentage) { + mBatteryPercentage = batteryPercentage; + return this; + } + + /** + * Sets the displayed connection strength of the remote device to the internet. + * + * @param connectionStrength Connection strength in range 0 to 3. + * @return Returns the Builder object. + */ + @NonNull + public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) { + mConnectionStrength = connectionStrength; + return this; + } + + /** + * Builds the {@link DeviceInfo} object. + * + * @return Returns the built {@link DeviceInfo} object. + */ + @NonNull + public DeviceInfo build() { + return new DeviceInfo(mDeviceType, mDeviceName, mModelName, mBatteryPercentage, + mConnectionStrength); + } + } + + private static void validate(int deviceType, String deviceName, String modelName, + int batteryPercentage, int connectionStrength) { + if (deviceType != DEVICE_TYPE_UNKNOWN && deviceType != DEVICE_TYPE_PHONE + && deviceType != DEVICE_TYPE_TABLET && deviceType != DEVICE_TYPE_LAPTOP) { + throw new IllegalArgumentException("Illegal device type"); + } + if (Objects.isNull(deviceName)) { + throw new IllegalArgumentException("DeviceName must be set"); + } + if (Objects.isNull(modelName)) { + throw new IllegalArgumentException("ModelName must be set"); + } + if (batteryPercentage < 0 || batteryPercentage > 100) { + throw new IllegalArgumentException("BatteryPercentage must be in range 0-100"); + } + if (connectionStrength < 0 || connectionStrength > 3) { + throw new IllegalArgumentException("ConnectionStrength must be in range 0-3"); + } + } + + private DeviceInfo(@DeviceType int deviceType, @NonNull String deviceName, + @NonNull String modelName, int batteryPercentage, int connectionStrength) { + validate(deviceType, deviceName, modelName, batteryPercentage, connectionStrength); + mDeviceType = deviceType; + mDeviceName = deviceName; + mModelName = modelName; + mBatteryPercentage = batteryPercentage; + mConnectionStrength = connectionStrength; + } + + /** + * Gets the device type that provides connectivity. + * + * @return Returns the device type as represented by IntDef {@link DeviceType}. + */ + @DeviceType + public int getDeviceType() { + return mDeviceType; + } + + /** + * Gets the device name of the remote device. + * + * @return Returns the user configurable device name. + */ + @NonNull + public String getDeviceName() { + return mDeviceName; + } + + /** + * Gets the model name of the remote device. + * + * @return Returns the OEM configured name for the device model. + */ + @NonNull + public String getModelName() { + return mModelName; + } + + /** + * Gets the battery charge percentage of the remote device. + * + * @return Returns the battery charge percentage in the range 0 to 100. + */ + @NonNull + @IntRange(from = 0, to = 100) + public int getBatteryPercentage() { + return mBatteryPercentage; + } + + /** + * Gets the displayed connection strength of the remote device to the internet. + * + * @return Returns the connection strength in range 0 to 3. + */ + @NonNull + @IntRange(from = 0, to = 3) + public int getConnectionStrength() { + return mConnectionStrength; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DeviceInfo)) return false; + DeviceInfo other = (DeviceInfo) obj; + return mDeviceType == other.getDeviceType() + && Objects.equals(mDeviceName, other.mDeviceName) + && Objects.equals(mModelName, other.mModelName) + && mBatteryPercentage == other.mBatteryPercentage + && mConnectionStrength == other.mConnectionStrength; + } + + @Override + public int hashCode() { + return Objects.hash(mDeviceType, mDeviceName, mModelName, mBatteryPercentage, + mConnectionStrength); + } + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDeviceType); + dest.writeString(mDeviceName); + dest.writeString(mModelName); + dest.writeInt(mBatteryPercentage); + dest.writeInt(mConnectionStrength); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator<DeviceInfo> CREATOR = new Creator<DeviceInfo>() { + @Override + public DeviceInfo createFromParcel(Parcel in) { + return new DeviceInfo(in.readInt(), in.readString(), in.readString(), in.readInt(), + in.readInt()); + } + + @Override + public DeviceInfo[] newArray(int size) { + return new DeviceInfo[size]; + } + }; + + @Override + public String toString() { + return new StringBuilder("DeviceInfo[") + .append("deviceType=").append(mDeviceType) + .append(", deviceName=").append(mDeviceName) + .append(", modelName=").append(mModelName) + .append(", batteryPercentage=").append(mBatteryPercentage) + .append(", connectionStrength=").append(mConnectionStrength) + .append("]").toString(); + } +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl new file mode 100644 index 000000000000..140d72ace70d --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +parcelable KnownNetwork; diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java new file mode 100644 index 000000000000..34b7e94e0fda --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java @@ -0,0 +1,258 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import static android.net.wifi.WifiAnnotations.SecurityType; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; + +/** + * A data class representing a known Wifi network. + * + * @hide + */ +@SystemApi +public final class KnownNetwork implements Parcelable { + /** + * Network is known by a nearby device with the same user account. + */ + public static final int NETWORK_SOURCE_NEARBY_SELF = 0; + + /** + * Network is known via cloud storage associated with this device's user account. + */ + public static final int NETWORK_SOURCE_CLOUD_SELF = 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + NETWORK_SOURCE_NEARBY_SELF, + NETWORK_SOURCE_CLOUD_SELF + }) + public @interface NetworkSource {} + + @NetworkSource private final int mNetworkSource; + private final String mSsid; + @SecurityType private final int[] mSecurityTypes; + private final DeviceInfo mDeviceInfo; + + /** + * Builder class for {@link KnownNetwork}. + */ + public static final class Builder { + @NetworkSource private int mNetworkSource = -1; + private String mSsid; + @SecurityType private int[] mSecurityTypes; + private android.net.wifi.sharedconnectivity.app.DeviceInfo mDeviceInfo; + + public Builder() {} + + /** + * Sets the indicated source of the known network. + * + * @param networkSource The network source as defined by IntDef {@link NetworkSource}. + * @return Returns the Builder object. + */ + @NonNull + public Builder setNetworkSource(@NetworkSource int networkSource) { + mNetworkSource = networkSource; + return this; + } + + /** + * Sets the SSID of the known network. + * + * @param ssid The SSID of the known network. Surrounded by double quotes if UTF-8. + * @return Returns the Builder object. + */ + @NonNull + public Builder setSsid(@NonNull String ssid) { + mSsid = ssid; + return this; + } + + /** + * Sets the security types of the known network. + * + * @param securityTypes The array of security types supported by the known network. + * @return Returns the Builder object. + */ + @NonNull + public Builder setSecurityTypes(@NonNull @SecurityType int[] securityTypes) { + mSecurityTypes = securityTypes; + return this; + } + + /** + * Sets the device information of the device providing connectivity. + * + * @param deviceInfo The array of security types supported by the known network. + * @return Returns the Builder object. + */ + @NonNull + public Builder setDeviceInfo(@NonNull DeviceInfo deviceInfo) { + mDeviceInfo = deviceInfo; + return this; + } + + /** + * Builds the {@link KnownNetwork} object. + * + * @return Returns the built {@link KnownNetwork} object. + */ + @NonNull + public KnownNetwork build() { + return new KnownNetwork( + mNetworkSource, + mSsid, + mSecurityTypes, + mDeviceInfo); + } + } + + private static void validate(int networkSource, String ssid, int [] securityTypes) { + if (networkSource != NETWORK_SOURCE_CLOUD_SELF && networkSource + != NETWORK_SOURCE_NEARBY_SELF) { + throw new IllegalArgumentException("Illegal network source"); + } + if (TextUtils.isEmpty(ssid)) { + throw new IllegalArgumentException("SSID must be set"); + } + if (securityTypes == null || securityTypes.length == 0) { + throw new IllegalArgumentException("SecurityTypes must be set"); + } + } + + private KnownNetwork( + @NetworkSource int networkSource, + @NonNull String ssid, + @NonNull @SecurityType int[] securityTypes, + @NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo deviceInfo) { + validate(networkSource, ssid, securityTypes); + mNetworkSource = networkSource; + mSsid = ssid; + mSecurityTypes = securityTypes; + mDeviceInfo = deviceInfo; + } + + /** + * Gets the indicated source of the known network. + * + * @return Returns the network source as defined by IntDef {@link NetworkSource}. + */ + @NonNull + @NetworkSource + public int getNetworkSource() { + return mNetworkSource; + } + + /** + * Gets the SSID of the known network. + * + * @return Returns the SSID of the known network. Surrounded by double quotes if UTF-8. + */ + @NonNull + public String getSsid() { + return mSsid; + } + + /** + * Gets the security types of the known network. + * + * @return Returns the array of security types supported by the known network. + */ + @NonNull + @SecurityType + public int[] getSecurityTypes() { + return mSecurityTypes; + } + + /** + * Gets the device information of the device providing connectivity. + * + * @return Returns the array of security types supported by the known network. + */ + @NonNull + public DeviceInfo getDeviceInfo() { + return mDeviceInfo; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof KnownNetwork)) return false; + KnownNetwork other = (KnownNetwork) obj; + return mNetworkSource == other.getNetworkSource() + && Objects.equals(mSsid, other.getSsid()) + && Arrays.equals(mSecurityTypes, other.getSecurityTypes()) + && Objects.equals(mDeviceInfo, other.getDeviceInfo()); + } + + @Override + public int hashCode() { + return Objects.hash(mNetworkSource, mSsid, Arrays.hashCode(mSecurityTypes), + mDeviceInfo.hashCode()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mNetworkSource); + dest.writeString(mSsid); + dest.writeIntArray(mSecurityTypes); + dest.writeTypedObject(mDeviceInfo, 0); + } + + @NonNull + public static final Creator<KnownNetwork> CREATOR = new Creator<>() { + @Override + public KnownNetwork createFromParcel(Parcel in) { + return new KnownNetwork(in.readInt(), in.readString(), in.createIntArray(), + in.readTypedObject(DeviceInfo.CREATOR)); + } + + @Override + public KnownNetwork[] newArray(int size) { + return new KnownNetwork[size]; + } + }; + + @Override + public String toString() { + return new StringBuilder("KnownNetwork[") + .append("NetworkSource=").append(mNetworkSource) + .append(", ssid=").append(mSsid) + .append(", securityTypes=").append(Arrays.toString(mSecurityTypes)) + .append(", deviceInfo=").append(mDeviceInfo.toString()) + .append("]").toString(); + } +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java new file mode 100644 index 000000000000..dcb5201b4594 --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java @@ -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 android.net.wifi.sharedconnectivity.app; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; + +import java.util.List; + +/** + * Interface for clients of {@link SharedConnectivityManager} to register for changes in network + * status. + * + * @hide + */ +@SystemApi +public interface SharedConnectivityClientCallback { + /** + * This method is being called by {@link SharedConnectivityService} to notify of a change in the + * list of available Tether Networks. + * @param networks Updated Tether Network list. + */ + void onTetherNetworksUpdated(@NonNull List<TetherNetwork> networks); + + /** + * This method is being called by {@link SharedConnectivityService} to notify of a change in the + * list of available Known Networks. + * @param networks Updated Known Network list. + */ + void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks); + + /** + * This method is being called by {@link SharedConnectivityService} to notify of a change in the + * state of share connectivity settings. + * @param state The new state. + */ + void onSharedConnectivitySettingsChanged(@NonNull SharedConnectivitySettingsState state); +} + diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java new file mode 100644 index 000000000000..b43e4f7f57b5 --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -0,0 +1,283 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.res.Resources; +import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback; +import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService; +import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.Log; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * This class is the library used by consumers of Shared Connectivity data to bind to the service, + * receive callbacks from, and send user actions to the service. + * + * @hide + */ +@SystemApi +public class SharedConnectivityManager { + private static final String TAG = SharedConnectivityManager.class.getSimpleName(); + private static final boolean DEBUG = true; + private static final String SERVICE_PACKAGE_NAME = "sharedconnectivity_service_package"; + private static final String SERVICE_CLASS_NAME = "sharedconnectivity_service_class"; + + private static final class SharedConnectivityCallbackProxy extends + ISharedConnectivityCallback.Stub { + private final Executor mExecutor; + private final SharedConnectivityClientCallback mCallback; + + SharedConnectivityCallbackProxy( + @NonNull @CallbackExecutor Executor executor, + @NonNull SharedConnectivityClientCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onTetherNetworksUpdated(@NonNull List<TetherNetwork> networks) { + if (mCallback != null) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onTetherNetworksUpdated(networks)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + @Override + public void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks) { + if (mCallback != null) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onKnownNetworksUpdated(networks)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + @Override + public void onSharedConnectivitySettingsChanged( + @NonNull SharedConnectivitySettingsState state) { + if (mCallback != null) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onSharedConnectivitySettingsChanged(state)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + } + + private ISharedConnectivityService mService; + private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy> + mProxyMap = new HashMap<>(); + + /** + * Constructor for new instance of {@link SharedConnectivityManager}. + * + * Automatically binds to implementation of {@link SharedConnectivityService} specified in + * device overlay. + */ + @SuppressLint("ManagerConstructor") + public SharedConnectivityManager(@NonNull Context context) { + ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mService = ISharedConnectivityService.Stub.asInterface(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Log.i(TAG, "onServiceDisconnected"); + mService = null; + mProxyMap.clear(); + } + }; + bind(context, serviceConnection); + } + + /** + * @hide + */ + @TestApi + public void setService(@Nullable IInterface service) { + mService = (ISharedConnectivityService) service; + } + + private void bind(Context context, ServiceConnection serviceConnection) { + Resources resources = context.getResources(); + int packageNameId = resources.getIdentifier(SERVICE_PACKAGE_NAME, "string", + context.getPackageName()); + int classNameId = resources.getIdentifier(SERVICE_CLASS_NAME, "string", + context.getPackageName()); + if (packageNameId == 0 || classNameId == 0) { + throw new Resources.NotFoundException("Package and class names for" + + " shared connectivity service must be defined"); + } + + Intent intent = new Intent(); + intent.setComponent(new ComponentName(resources.getString(packageNameId), + resources.getString(classNameId))); + context.bindService( + intent, + serviceConnection, Context.BIND_AUTO_CREATE); + } + + /** + * Registers a callback for receiving updates to the list of Tether Networks and Known Networks. + * + * @param executor The Executor used to invoke the callback. + * @param callback The callback of type {@link SharedConnectivityClientCallback} that is invoked + * when the service updates either the list of Tether Networks or Known + * Networks. + * @return Returns true if the registration was successful, false otherwise. + */ + public boolean registerCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull SharedConnectivityClientCallback callback) { + Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); + if (mService == null || mProxyMap.containsKey(callback)) return false; + try { + SharedConnectivityCallbackProxy proxy = + new SharedConnectivityCallbackProxy(executor, callback); + mService.registerCallback(proxy); + mProxyMap.put(callback, proxy); + } catch (RemoteException e) { + Log.e(TAG, "Exception in registerCallback", e); + return false; + } + return true; + } + + /** + * Unregisters a callback. + * + * @return Returns true if the callback was successfully unregistered, false otherwise. + */ + public boolean unregisterCallback( + @NonNull SharedConnectivityClientCallback callback) { + Objects.requireNonNull(callback, "callback cannot be null"); + if (mService == null || !mProxyMap.containsKey(callback)) return false; + try { + mService.unregisterCallback(mProxyMap.get(callback)); + mProxyMap.remove(callback); + } catch (RemoteException e) { + Log.e(TAG, "Exception in unregisterCallback", e); + return false; + } + return true; + } + + /** + * Send command to the implementation of {@link SharedConnectivityService} requesting connection + * to the specified Tether Network. + * + * @param network {@link TetherNetwork} object representing the network the user has requested + * a connection to. + * @return Returns true if the service received the command. Does not guarantee that the + * connection was successful. + */ + public boolean connectTetherNetwork(@NonNull TetherNetwork network) { + if (mService == null) return false; + try { + mService.connectTetherNetwork(network); + } catch (RemoteException e) { + Log.e(TAG, "Exception in connectTetherNetwork", e); + return false; + } + return true; + } + + /** + * Send command to the implementation of {@link SharedConnectivityService} requesting + * disconnection from the active Tether Network. + * + * @return Returns true if the service received the command. Does not guarantee that the + * disconnection was successful. + */ + public boolean disconnectTetherNetwork() { + if (mService == null) return false; + try { + mService.disconnectTetherNetwork(); + } catch (RemoteException e) { + Log.e(TAG, "Exception in disconnectTetherNetwork", e); + return false; + } + return true; + } + + /** + * Send command to the implementation of {@link SharedConnectivityService} requesting connection + * to the specified Known Network. + * + * @param network {@link KnownNetwork} object representing the network the user has requested + * a connection to. + * @return Returns true if the service received the command. Does not guarantee that the + * connection was successful. + */ + public boolean connectKnownNetwork(@NonNull KnownNetwork network) { + if (mService == null) return false; + try { + mService.connectKnownNetwork(network); + } catch (RemoteException e) { + Log.e(TAG, "Exception in connectKnownNetwork", e); + return false; + } + return true; + } + + /** + * Send command to the implementation of {@link SharedConnectivityService} requesting removal of + * the specified Known Network from the list of Known Networks. + * + * @return Returns true if the service received the command. Does not guarantee that the + * forget action was successful. + */ + public boolean forgetKnownNetwork(@NonNull KnownNetwork network) { + if (mService == null) return false; + try { + mService.forgetKnownNetwork(network); + } catch (RemoteException e) { + Log.e(TAG, "Exception in forgetKnownNetwork", e); + return false; + } + return true; + } +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl new file mode 100644 index 000000000000..289afacb9aa0 --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +parcelable SharedConnectivitySettingsState;
\ No newline at end of file diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java new file mode 100644 index 000000000000..dd2fa94ccf2d --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java @@ -0,0 +1,154 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + + +/** + * A data class representing the shared connectivity settings state. + * + * This class represents a snapshot of the settings and can be out of date if the settings changed + * after receiving an object of this class. + * + * @hide + */ +@SystemApi +public final class SharedConnectivitySettingsState implements Parcelable { + + private final boolean mInstantTetherEnabled; + private final Bundle mExtras; + + /** + * Builder class for {@link SharedConnectivitySettingsState}. + */ + public static final class Builder { + private boolean mInstantTetherEnabled; + private Bundle mExtras; + + public Builder() {} + + /** + * Sets the state of Instant Tether in settings + * + * @return Returns the Builder object. + */ + @NonNull + public Builder setInstantTetherEnabled(boolean instantTetherEnabled) { + mInstantTetherEnabled = instantTetherEnabled; + return this; + } + + /** + * Sets the extras bundle + * + * @return Returns the Builder object. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Builds the {@link SharedConnectivitySettingsState} object. + * + * @return Returns the built {@link SharedConnectivitySettingsState} object. + */ + @NonNull + public SharedConnectivitySettingsState build() { + return new SharedConnectivitySettingsState(mInstantTetherEnabled, mExtras); + } + } + + private SharedConnectivitySettingsState(boolean instantTetherEnabled, Bundle extras) { + mInstantTetherEnabled = instantTetherEnabled; + mExtras = extras; + } + + /** + * Gets the state of Instant Tether in settings + * + * @return Returns true for enabled, false otherwise. + */ + @NonNull + public boolean isInstantTetherEnabled() { + return mInstantTetherEnabled; + } + + /** + * Gets the extras Bundle. + * + * @return Returns a Bundle object. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SharedConnectivitySettingsState)) return false; + SharedConnectivitySettingsState other = (SharedConnectivitySettingsState) obj; + return mInstantTetherEnabled == other.isInstantTetherEnabled(); + } + + @Override + public int hashCode() { + return Objects.hash(mInstantTetherEnabled); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mInstantTetherEnabled); + dest.writeBundle(mExtras); + } + + @NonNull + public static final Creator<SharedConnectivitySettingsState> CREATOR = + new Creator<SharedConnectivitySettingsState>() { + @Override + public SharedConnectivitySettingsState createFromParcel(Parcel in) { + return new SharedConnectivitySettingsState(in.readBoolean(), + in.readBundle(getClass().getClassLoader())); + } + + @Override + public SharedConnectivitySettingsState[] newArray(int size) { + return new SharedConnectivitySettingsState[size]; + } + }; + + @Override + public String toString() { + return new StringBuilder("SharedConnectivitySettingsState[") + .append("instantTetherEnabled=").append(mInstantTetherEnabled) + .append("extras=").append(mExtras.toString()) + .append("]").toString(); + } +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.aidl new file mode 100644 index 000000000000..6cc4cfe7dce5 --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +parcelable TetherNetwork; diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java new file mode 100644 index 000000000000..bbdad5344646 --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java @@ -0,0 +1,369 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import static android.net.wifi.WifiAnnotations.SecurityType; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; +import android.os.Parcel; +import android.os.Parcelable; + + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; + +/** + * A data class representing an Instant Tether network. + * This class is used in IPC calls between the implementer of {@link SharedConnectivityService} and + * the consumers of {@link com.android.wifitrackerlib}. + * + * @hide + */ +@SystemApi +public final class TetherNetwork implements Parcelable { + /** + * Remote device is connected to the internet via an unknown connection. + */ + public static final int NETWORK_TYPE_UNKNOWN = 0; + + /** + * Remote device is connected to the internet via a cellular connection. + */ + public static final int NETWORK_TYPE_CELLULAR = 1; + + /** + * Remote device is connected to the internet via a Wi-Fi connection. + */ + public static final int NETWORK_TYPE_WIFI = 2; + + /** + * Remote device is connected to the internet via an ethernet connection. + */ + public static final int NETWORK_TYPE_ETHERNET = 3; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + NETWORK_TYPE_UNKNOWN, + NETWORK_TYPE_CELLULAR, + NETWORK_TYPE_WIFI, + NETWORK_TYPE_ETHERNET + }) + public @interface NetworkType {} + + private final long mDeviceId; + private final DeviceInfo mDeviceInfo; + @NetworkType private final int mNetworkType; + private final String mNetworkName; + @Nullable private final String mHotspotSsid; + @Nullable private final String mHotspotBssid; + @Nullable @SecurityType private final int[] mHotspotSecurityTypes; + + /** + * Builder class for {@link TetherNetwork}. + */ + public static final class Builder { + private long mDeviceId = -1; + private DeviceInfo mDeviceInfo; + @NetworkType private int mNetworkType; + private String mNetworkName; + @Nullable private String mHotspotSsid; + @Nullable private String mHotspotBssid; + @Nullable @SecurityType private int[] mHotspotSecurityTypes; + + public Builder() {} + + /** + * Set the remote device ID. + * + * @param deviceId Locally unique ID for this Instant Tether network. + * @return Returns the Builder object. + */ + @NonNull + public Builder setDeviceId(long deviceId) { + mDeviceId = deviceId; + return this; + } + + /** + * Sets information about the device providing connectivity. + * + * @param deviceInfo The user configurable device name. + * @return Returns the Builder object. + */ + @NonNull + public Builder setDeviceInfo(@NonNull DeviceInfo deviceInfo) { + mDeviceInfo = deviceInfo; + return this; + } + + /** + * Sets the network type that the remote device is connected to. + * + * @param networkType Network type as represented by IntDef {@link NetworkType}. + * @return Returns the Builder object. + */ + @NonNull + public Builder setNetworkType(@NetworkType int networkType) { + mNetworkType = networkType; + return this; + } + + /** + * Sets the display name of the network the remote device is connected to. + * + * @param networkName Network display name. (e.g. "Google Fi", "Hotel WiFi", "Ethernet") + * @return Returns the Builder object. + */ + @NonNull + public Builder setNetworkName(@NonNull String networkName) { + mNetworkName = networkName; + return this; + } + + /** + * Sets the hotspot SSID being broadcast by the remote device, or null if hotspot is off. + * + * @param hotspotSsid The SSID of the hotspot. Surrounded by double quotes if UTF-8. + * @return Returns the Builder object. + */ + @NonNull + public Builder setHotspotSsid(@NonNull String hotspotSsid) { + mHotspotSsid = hotspotSsid; + return this; + } + + /** + * Sets the hotspot BSSID being broadcast by the remote device, or null if hotspot is off. + * + * @param hotspotBssid The BSSID of the hotspot. + * @return Returns the Builder object. + */ + @NonNull + public Builder setHotspotBssid(@NonNull String hotspotBssid) { + mHotspotBssid = hotspotBssid; + return this; + } + + /** + * Sets the hotspot security types supported by the remote device, or null if hotspot is + * off. + * + * @param hotspotSecurityTypes The array of security types supported by the hotspot. + * @return Returns the Builder object. + */ + @NonNull + public Builder setHotspotSecurityTypes(@NonNull @SecurityType int[] hotspotSecurityTypes) { + mHotspotSecurityTypes = hotspotSecurityTypes; + return this; + } + + /** + * Builds the {@link TetherNetwork} object. + * + * @return Returns the built {@link TetherNetwork} object. + */ + @NonNull + public TetherNetwork build() { + return new TetherNetwork( + mDeviceId, + mDeviceInfo, + mNetworkType, + mNetworkName, + mHotspotSsid, + mHotspotBssid, + mHotspotSecurityTypes); + } + } + + private static void validate(long deviceId, int networkType, String networkName) { + if (deviceId < 0) { + throw new IllegalArgumentException("DeviceId must be set"); + } + if (networkType != NETWORK_TYPE_CELLULAR && networkType != NETWORK_TYPE_WIFI + && networkType != NETWORK_TYPE_ETHERNET && networkType != NETWORK_TYPE_UNKNOWN) { + throw new IllegalArgumentException("Illegal network type"); + } + if (Objects.isNull(networkName)) { + throw new IllegalArgumentException("NetworkName must be set"); + } + } + + private TetherNetwork( + long deviceId, + DeviceInfo deviceInfo, + @NetworkType int networkType, + @NonNull String networkName, + @Nullable String hotspotSsid, + @Nullable String hotspotBssid, + @Nullable @SecurityType int[] hotspotSecurityTypes) { + validate(deviceId, + networkType, + networkName); + mDeviceId = deviceId; + mDeviceInfo = deviceInfo; + mNetworkType = networkType; + mNetworkName = networkName; + mHotspotSsid = hotspotSsid; + mHotspotBssid = hotspotBssid; + mHotspotSecurityTypes = hotspotSecurityTypes; + } + + /** + * Gets the remote device ID. + * + * @return Returns the locally unique ID for this Instant Tether network. + */ + @NonNull + public long getDeviceId() { + return mDeviceId; + } + + /** + * Gets information about the device providing connectivity. + * + * @return Returns the locally unique ID for this Instant Tether network. + */ + @NonNull + public DeviceInfo getDeviceInfo() { + return mDeviceInfo; + } + + /** + * Gets the network type that the remote device is connected to. + * + * @return Returns the network type as represented by IntDef {@link NetworkType}. + */ + @NonNull + @NetworkType + public int getNetworkType() { + return mNetworkType; + } + + /** + * Gets the display name of the network the remote device is connected to. + * + * @return Returns the network display name. (e.g. "Google Fi", "Hotel WiFi", "Ethernet") + */ + @NonNull + public String getNetworkName() { + return mNetworkName; + } + + /** + * Gets the hotspot SSID being broadcast by the remote device, or null if hotspot is off. + * + * @return Returns the SSID of the hotspot. Surrounded by double quotes if UTF-8. + */ + @Nullable + public String getHotspotSsid() { + return mHotspotSsid; + } + + /** + * Gets the hotspot BSSID being broadcast by the remote device, or null if hotspot is off. + * + * @return Returns the BSSID of the hotspot. + */ + @Nullable + public String getHotspotBssid() { + return mHotspotBssid; + } + + /** + * Gets the hotspot security types supported by the remote device. + * + * @return Returns the array of security types supported by the hotspot. + */ + @Nullable + @SecurityType + public int[] getHotspotSecurityTypes() { + return mHotspotSecurityTypes; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TetherNetwork)) return false; + TetherNetwork other = (TetherNetwork) obj; + return mDeviceId == other.getDeviceId() + && Objects.equals(mDeviceInfo, other.getDeviceInfo()) + && mNetworkType == other.getNetworkType() + && Objects.equals(mNetworkName, other.getNetworkName()) + && Objects.equals(mHotspotSsid, other.getHotspotSsid()) + && Objects.equals(mHotspotBssid, other.getHotspotBssid()) + && Arrays.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes()); + } + + @Override + public int hashCode() { + return Objects.hash(mDeviceId, mDeviceInfo, mNetworkName, mHotspotSsid, mHotspotBssid, + Arrays.hashCode(mHotspotSecurityTypes)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mDeviceId); + dest.writeTypedObject(mDeviceInfo, 0); + dest.writeInt(mNetworkType); + dest.writeString(mNetworkName); + dest.writeString(mHotspotSsid); + dest.writeString(mHotspotBssid); + dest.writeIntArray(mHotspotSecurityTypes); + } + + @NonNull + public static final Creator<TetherNetwork> CREATOR = new Creator<>() { + @Override + public TetherNetwork createFromParcel(Parcel in) { + return new TetherNetwork(in.readLong(), in.readTypedObject( + android.net.wifi.sharedconnectivity.app.DeviceInfo.CREATOR), + in.readInt(), in.readString(), in.readString(), in.readString(), + in.createIntArray()); + } + + @Override + public TetherNetwork[] newArray(int size) { + return new TetherNetwork[size]; + } + }; + + @Override + public String toString() { + return new StringBuilder("TetherNetwork[") + .append("deviceId=").append(mDeviceId) + .append(", networkType=").append(mNetworkType) + .append(", deviceInfo=").append(mDeviceInfo.toString()) + .append(", networkName=").append(mNetworkName) + .append(", hotspotSsid=").append(mHotspotSsid) + .append(", hotspotBssid=").append(mHotspotBssid) + .append(", hotspotSecurityTypes=").append(Arrays.toString(mHotspotSecurityTypes)) + .append("]").toString(); + } +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl new file mode 100644 index 000000000000..6e5613886cdc --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl @@ -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 android.net.wifi.sharedconnectivity.service; + +import android.net.wifi.sharedconnectivity.app.KnownNetwork; +import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState; +import android.net.wifi.sharedconnectivity.app.TetherNetwork; + +/* + * @hide + */ +interface ISharedConnectivityCallback { + oneway void onTetherNetworksUpdated(in List<TetherNetwork> networks); + oneway void onKnownNetworksUpdated(in List<KnownNetwork> networks); + oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state); +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl new file mode 100644 index 000000000000..5d79405ba19a --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl @@ -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 android.net.wifi.sharedconnectivity.service; + +import android.net.wifi.sharedconnectivity.app.KnownNetwork; +import android.net.wifi.sharedconnectivity.app.TetherNetwork; +import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback; + +/* + * @hide + */ +interface ISharedConnectivityService { + void registerCallback(in ISharedConnectivityCallback callback); + void unregisterCallback(in ISharedConnectivityCallback callback); + void connectTetherNetwork(in TetherNetwork network); + void disconnectTetherNetwork(); + void connectKnownNetwork(in KnownNetwork network); + void forgetKnownNetwork(in KnownNetwork network); +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java new file mode 100644 index 000000000000..234319ad5318 --- /dev/null +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -0,0 +1,287 @@ +/* + * 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 android.net.wifi.sharedconnectivity.service; + +import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.Manifest.permission.NETWORK_SETUP_WIZARD; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.wifi.sharedconnectivity.app.KnownNetwork; +import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager; +import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState; +import android.net.wifi.sharedconnectivity.app.TetherNetwork; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * This class is the partly implemented service for injecting Shared Connectivity networks into the + * Wi-Fi Pickers and other relevant UI surfaces. + * + * Implementing application should extend this service and override the indicated methods. + * Callers to the service should use {@link SharedConnectivityManager} to bind to the implemented + * service as specified in the configuration overlay. + * + * @hide + */ +@SystemApi +public abstract class SharedConnectivityService extends Service { + private static final String TAG = SharedConnectivityService.class.getSimpleName(); + private static final boolean DEBUG = true; + + private final Handler mHandler; + private final List<ISharedConnectivityCallback> mCallbacks = new ArrayList<>(); + // Used to find DeathRecipient when unregistering a callback to call unlinkToDeath. + private final Map<ISharedConnectivityCallback, DeathRecipient> mDeathRecipientMap = + new HashMap<>(); + + private List<TetherNetwork> mTetherNetworks = Collections.emptyList(); + private List<KnownNetwork> mKnownNetworks = Collections.emptyList(); + private SharedConnectivitySettingsState mSettingsState; + + public SharedConnectivityService() { + mHandler = new Handler(getMainLooper()); + } + + public SharedConnectivityService(@NonNull Handler handler) { + mHandler = handler; + } + + private final class DeathRecipient implements IBinder.DeathRecipient { + ISharedConnectivityCallback mCallback; + + DeathRecipient(ISharedConnectivityCallback callback) { + mCallback = callback; + } + + @Override + public void binderDied() { + mCallbacks.remove(mCallback); + mDeathRecipientMap.remove(mCallback); + } + } + + @Override + @Nullable + public IBinder onBind(@NonNull Intent intent) { + if (DEBUG) Log.i(TAG, "onBind intent=" + intent); + return new ISharedConnectivityService.Stub() { + @Override + public void registerCallback(ISharedConnectivityCallback callback) { + checkPermissions(); + mHandler.post(() -> registerCallback(callback)); + } + + @Override + public void unregisterCallback(ISharedConnectivityCallback callback) { + checkPermissions(); + mHandler.post(() -> unregisterCallback(callback)); + } + + @Override + public void connectTetherNetwork(TetherNetwork network) { + checkPermissions(); + mHandler.post(() -> onConnectTetherNetwork(network)); + } + + @Override + public void disconnectTetherNetwork() { + checkPermissions(); + mHandler.post(() -> onDisconnectTetherNetwork()); + } + + @Override + public void connectKnownNetwork(KnownNetwork network) { + checkPermissions(); + mHandler.post(() -> onConnectKnownNetwork(network)); + } + + @Override + public void forgetKnownNetwork(KnownNetwork network) { + checkPermissions(); + mHandler.post(() -> onForgetKnownNetwork(network)); + } + + @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD}) + private void checkPermissions() { + if (checkCallingPermission(NETWORK_SETTINGS) != PackageManager.PERMISSION_GRANTED + && checkCallingPermission(NETWORK_SETUP_WIZARD) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Calling process must have NETWORK_SETTINGS or" + + " NETWORK_SETUP_WIZARD permission"); + } + } + }; + } + + private void registerCallback(ISharedConnectivityCallback callback) { + // Listener gets triggered on first register using cashed data + if (!notifyTetherNetworkUpdate(callback) || !notifyKnownNetworkUpdate(callback)) { + if (DEBUG) Log.w(TAG, "Failed to notify client"); + return; + } + + DeathRecipient deathRecipient = new DeathRecipient(callback); + try { + callback.asBinder().linkToDeath(deathRecipient, 0); + mCallbacks.add(callback); + mDeathRecipientMap.put(callback, deathRecipient); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in registerCallback", e); + } + } + + private void unregisterCallback(ISharedConnectivityCallback callback) { + DeathRecipient deathRecipient = mDeathRecipientMap.get(callback); + if (deathRecipient != null) { + callback.asBinder().unlinkToDeath(deathRecipient, 0); + mDeathRecipientMap.remove(callback); + } + mCallbacks.remove(callback); + } + + private boolean notifyTetherNetworkUpdate(ISharedConnectivityCallback callback) { + try { + callback.onTetherNetworksUpdated(mTetherNetworks); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in notifyTetherNetworkUpdate", e); + return false; + } + return true; + } + + private boolean notifyKnownNetworkUpdate(ISharedConnectivityCallback callback) { + try { + callback.onKnownNetworksUpdated(mKnownNetworks); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in notifyKnownNetworkUpdate", e); + return false; + } + return true; + } + + private boolean notifySettingsStateUpdate(ISharedConnectivityCallback callback) { + try { + callback.onSharedConnectivitySettingsChanged(mSettingsState); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in notifySettingsStateUpdate", e); + return false; + } + return true; + } + + /** + * Implementing application should call this method to provide an up-to-date list of Tether + * Networks to be displayed to the user. + * + * This method updates the cached list and notifies all registered callbacks. Any callbacks that + * are inaccessible will be unregistered. + * + * @param networks The updated list of {@link TetherNetwork} objects. + */ + public final void setTetherNetworks(@NonNull List<TetherNetwork> networks) { + mTetherNetworks = networks; + + for (ISharedConnectivityCallback callback:mCallbacks) { + notifyTetherNetworkUpdate(callback); + } + } + + /** + * Implementing application should call this method to provide an up-to-date list of Known + * Networks to be displayed to the user. + * + * This method updates the cached list and notifies all registered callbacks. Any callbacks that + * are inaccessible will be unregistered. + * + * @param networks The updated list of {@link KnownNetwork} objects. + */ + public final void setKnownNetworks(@NonNull List<KnownNetwork> networks) { + mKnownNetworks = networks; + + for (ISharedConnectivityCallback callback:mCallbacks) { + notifyKnownNetworkUpdate(callback); + } + } + + /** + * Implementing application should call this method to provide an up-to-date state of Shared + * connectivity settings state. + * + * This method updates the cached state and notifies all registered callbacks. Any callbacks + * that are inaccessible will be unregistered. + * + * @param settingsState The updated state {@link SharedConnectivitySettingsState} + * objects. + */ + public final void setSettingsState(@NonNull SharedConnectivitySettingsState settingsState) { + mSettingsState = settingsState; + + for (ISharedConnectivityCallback callback:mCallbacks) { + notifySettingsStateUpdate(callback); + } + } + + /** + * Implementing application should implement this method. + * + * Implementation should initiate a connection to the Tether Network indicated. + * + * @param network Object identifying the Tether Network the user has requested a connection to. + */ + public abstract void onConnectTetherNetwork(@NonNull TetherNetwork network); + + /** + * Implementing application should implement this method. + * + * Implementation should initiate a disconnection from the active Tether Network. + */ + public abstract void onDisconnectTetherNetwork(); + + /** + * Implementing application should implement this method. + * + * Implementation should initiate a connection to the Known Network indicated. + * + * @param network Object identifying the Known Network the user has requested a connection to. + */ + public abstract void onConnectKnownNetwork(@NonNull KnownNetwork network); + + /** + * Implementing application should implement this method. + * + * Implementation should remove the Known Network indicated from the synced list of networks. + * + * @param network Object identifying the Known Network the user has requested to forget. + */ + public abstract void onForgetKnownNetwork(@NonNull KnownNetwork network); +} diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java new file mode 100644 index 000000000000..f8f07008e34b --- /dev/null +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java @@ -0,0 +1,116 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_LAPTOP; +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit tests for {@link android.app.sharedconnectivity.DeviceInfo}. + */ +@SmallTest +public class DeviceInfoTest { + + private static final int DEVICE_TYPE = DEVICE_TYPE_PHONE; + private static final String DEVICE_NAME = "TEST_NAME"; + private static final String DEVICE_MODEL = "TEST_MODEL"; + private static final int BATTERY_PERCENTAGE = 50; + private static final int CONNECTION_STRENGTH = 2; + + private static final int DEVICE_TYPE_1 = DEVICE_TYPE_LAPTOP; + private static final String DEVICE_NAME_1 = "TEST_NAME1"; + private static final String DEVICE_MODEL_1 = "TEST_MODEL1"; + private static final int BATTERY_PERCENTAGE_1 = 30; + private static final int CONNECTION_STRENGTH_1 = 1; + + /** + * Verifies parcel serialization/deserialization. + */ + @Test + public void testParcelOperation() { + DeviceInfo info = buildDeviceInfoBuilder().build(); + + Parcel parcelW = Parcel.obtain(); + info.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + DeviceInfo fromParcel = DeviceInfo.CREATOR.createFromParcel(parcelR); + + assertEquals(info, fromParcel); + assertEquals(info.hashCode(), fromParcel.hashCode()); + } + + /** + * Verifies the Equals operation + */ + @Test + public void testEqualsOperation() { + DeviceInfo info1 = buildDeviceInfoBuilder().build(); + DeviceInfo info2 = buildDeviceInfoBuilder().build(); + assertEquals(info1, info2); + + DeviceInfo.Builder builder = buildDeviceInfoBuilder().setDeviceType(DEVICE_TYPE_1); + assertNotEquals(info1, builder.build()); + + builder = buildDeviceInfoBuilder().setDeviceName(DEVICE_NAME_1); + assertNotEquals(info1, builder.build()); + + builder = buildDeviceInfoBuilder().setModelName(DEVICE_MODEL_1); + assertNotEquals(info1, builder.build()); + + builder = buildDeviceInfoBuilder() + .setBatteryPercentage(BATTERY_PERCENTAGE_1); + assertNotEquals(info1, builder.build()); + + builder = buildDeviceInfoBuilder() + .setConnectionStrength(CONNECTION_STRENGTH_1); + assertNotEquals(info1, builder.build()); + } + + /** + * Verifies the get methods return the expected data. + */ + @Test + public void testGetMethods() { + DeviceInfo info = buildDeviceInfoBuilder().build(); + assertEquals(info.getDeviceType(), DEVICE_TYPE); + assertEquals(info.getDeviceName(), DEVICE_NAME); + assertEquals(info.getModelName(), DEVICE_MODEL); + assertEquals(info.getBatteryPercentage(), BATTERY_PERCENTAGE); + assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH); + assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH); + } + + private DeviceInfo.Builder buildDeviceInfoBuilder() { + return new DeviceInfo.Builder().setDeviceType(DEVICE_TYPE).setDeviceName(DEVICE_NAME) + .setModelName(DEVICE_MODEL).setBatteryPercentage(BATTERY_PERCENTAGE) + .setConnectionStrength(CONNECTION_STRENGTH); + } +} diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java new file mode 100644 index 000000000000..266afcc9a1a6 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java @@ -0,0 +1,115 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import static android.net.wifi.WifiInfo.SECURITY_TYPE_PSK; +import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP; +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE; +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET; +import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_CLOUD_SELF; +import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit tests for {@link android.app.sharedconnectivity.KnownNetwork}. + */ +@SmallTest +public class KnownNetworkTest { + + private static final int NETWORK_SOURCE = NETWORK_SOURCE_NEARBY_SELF; + private static final String SSID = "TEST_SSID"; + private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP}; + private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder() + .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL") + .setConnectionStrength(2).setBatteryPercentage(50).build(); + private static final int NETWORK_SOURCE_1 = NETWORK_SOURCE_CLOUD_SELF; + private static final String SSID_1 = "TEST_SSID1"; + private static final int[] SECURITY_TYPES_1 = {SECURITY_TYPE_PSK}; + private static final DeviceInfo DEVICE_INFO_1 = new DeviceInfo.Builder() + .setDeviceType(DEVICE_TYPE_PHONE).setDeviceName("TEST_NAME_1") + .setModelName("TEST_MODEL_1").setConnectionStrength(3).setBatteryPercentage(33).build(); + + /** + * Verifies parcel serialization/deserialization. + */ + @Test + public void testParcelOperation() { + KnownNetwork network = buildKnownNetworkBuilder().build(); + + Parcel parcelW = Parcel.obtain(); + network.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + KnownNetwork fromParcel = KnownNetwork.CREATOR.createFromParcel(parcelR); + + assertEquals(network, fromParcel); + assertEquals(network.hashCode(), fromParcel.hashCode()); + } + + /** + * Verifies the Equals operation + */ + @Test + public void testEqualsOperation() { + KnownNetwork network1 = buildKnownNetworkBuilder().build(); + KnownNetwork network2 = buildKnownNetworkBuilder().build(); + assertEquals(network1, network2); + + KnownNetwork.Builder builder = buildKnownNetworkBuilder() + .setNetworkSource(NETWORK_SOURCE_1); + assertNotEquals(network1, builder.build()); + + builder = buildKnownNetworkBuilder().setSsid(SSID_1); + assertNotEquals(network1, builder.build()); + + builder = buildKnownNetworkBuilder().setSecurityTypes(SECURITY_TYPES_1); + assertNotEquals(network1, builder.build()); + + builder = buildKnownNetworkBuilder().setDeviceInfo(DEVICE_INFO_1); + assertNotEquals(network1, builder.build()); + } + + /** + * Verifies the get methods return the expected data. + */ + @Test + public void testGetMethods() { + KnownNetwork network = buildKnownNetworkBuilder().build(); + assertEquals(network.getNetworkSource(), NETWORK_SOURCE); + assertEquals(network.getSsid(), SSID); + assertArrayEquals(network.getSecurityTypes(), SECURITY_TYPES); + assertEquals(network.getDeviceInfo(), DEVICE_INFO); + } + + private KnownNetwork.Builder buildKnownNetworkBuilder() { + return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID) + .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO); + } +} diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java new file mode 100644 index 000000000000..9aeccac1968e --- /dev/null +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java @@ -0,0 +1,257 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP; +import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP; +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET; +import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF; +import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService; +import android.os.RemoteException; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.Executor; + +/** + * Unit tests for {@link SharedConnectivityManager}. + */ +@SmallTest +public class SharedConnectivityManagerTest { + private static final long DEVICE_ID = 11L; + private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder() + .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL") + .setConnectionStrength(2).setBatteryPercentage(50).build(); + private static final int NETWORK_TYPE = NETWORK_TYPE_CELLULAR; + private static final String NETWORK_NAME = "TEST_NETWORK"; + private static final String HOTSPOT_SSID = "TEST_SSID"; + private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP}; + + private static final int NETWORK_SOURCE = NETWORK_SOURCE_NEARBY_SELF; + private static final String SSID = "TEST_SSID"; + private static final int[] SECURITY_TYPES = {SECURITY_TYPE_WEP}; + + private static final int SERVICE_PACKAGE_ID = 1; + private static final int SERVICE_CLASS_ID = 2; + + private static final String SERVICE_PACKAGE_NAME = "TEST_PACKAGE"; + private static final String SERVICE_CLASS_NAME = "TEST_CLASS"; + private static final String PACKAGE_NAME = "TEST_PACKAGE"; + + @Mock Context mContext; + @Mock + ISharedConnectivityService mService; + @Mock Executor mExecutor; + @Mock + SharedConnectivityClientCallback mClientCallback; + @Mock Resources mResources; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + setResources(mContext); + } + + /** + * Verifies constructor is binding to service. + */ + @Test + public void testBindingToService() { + new SharedConnectivityManager(mContext); + verify(mContext).bindService(any(), any(), anyInt()); + } + + /** + * Verifies callback is registered in the service only once and only when service is not null. + */ + @Test + public void testRegisterCallback() throws Exception { + SharedConnectivityManager manager = new SharedConnectivityManager(mContext); + manager.setService(null); + assertFalse(manager.registerCallback(mExecutor, mClientCallback)); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + assertTrue(manager.registerCallback(mExecutor, mClientCallback)); + verify(mService).registerCallback(any()); + + // Registering the same callback twice should fail. + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + manager.registerCallback(mExecutor, mClientCallback); + assertFalse(manager.registerCallback(mExecutor, mClientCallback)); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + doThrow(new RemoteException()).when(mService).registerCallback(any()); + assertFalse(manager.registerCallback(mExecutor, mClientCallback)); + } + + /** + * Verifies callback is unregistered from the service if it was registered before and only when + * service is not null. + */ + @Test + public void testUnregisterCallback() throws Exception { + SharedConnectivityManager manager = new SharedConnectivityManager(mContext); + manager.setService(null); + assertFalse(manager.unregisterCallback(mClientCallback)); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + manager.registerCallback(mExecutor, mClientCallback); + assertTrue(manager.unregisterCallback(mClientCallback)); + verify(mService).unregisterCallback(any()); + + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + manager.registerCallback(mExecutor, mClientCallback); + manager.unregisterCallback(mClientCallback); + assertFalse(manager.unregisterCallback(mClientCallback)); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + doThrow(new RemoteException()).when(mService).unregisterCallback(any()); + assertFalse(manager.unregisterCallback(mClientCallback)); + } + + /** + * Verifies service is called when not null and exceptions are handles when calling + * connectTetherNetwork. + */ + @Test + public void testConnectTetherNetwork() throws RemoteException { + TetherNetwork network = buildTetherNetwork(); + + SharedConnectivityManager manager = new SharedConnectivityManager(mContext); + manager.setService(null); + assertFalse(manager.connectTetherNetwork(network)); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + manager.connectTetherNetwork(network); + verify(mService).connectTetherNetwork(network); + + doThrow(new RemoteException()).when(mService).connectTetherNetwork(network); + assertFalse(manager.connectTetherNetwork(network)); + } + + /** + * Verifies service is called when not null and exceptions are handles when calling + * disconnectTetherNetwork. + */ + @Test + public void testDisconnectTetherNetwork() throws RemoteException { + SharedConnectivityManager manager = new SharedConnectivityManager(mContext); + manager.setService(null); + assertFalse(manager.disconnectTetherNetwork()); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + manager.disconnectTetherNetwork(); + verify(mService).disconnectTetherNetwork(); + + doThrow(new RemoteException()).when(mService).disconnectTetherNetwork(); + assertFalse(manager.disconnectTetherNetwork()); + } + + /** + * Verifies service is called when not null and exceptions are handles when calling + * connectKnownNetwork. + */ + @Test + public void testConnectKnownNetwork() throws RemoteException { + KnownNetwork network = buildKnownNetwork(); + + SharedConnectivityManager manager = new SharedConnectivityManager(mContext); + manager.setService(null); + assertFalse(manager.connectKnownNetwork(network)); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + manager.connectKnownNetwork(network); + verify(mService).connectKnownNetwork(network); + + doThrow(new RemoteException()).when(mService).connectKnownNetwork(network); + assertFalse(manager.connectKnownNetwork(network)); + } + + /** + * Verifies service is called when not null and exceptions are handles when calling + * forgetKnownNetwork. + */ + @Test + public void testForgetKnownNetwork() throws RemoteException { + KnownNetwork network = buildKnownNetwork(); + + SharedConnectivityManager manager = new SharedConnectivityManager(mContext); + manager.setService(null); + assertFalse(manager.forgetKnownNetwork(network)); + + manager = new SharedConnectivityManager(mContext); + manager.setService(mService); + manager.forgetKnownNetwork(network); + verify(mService).forgetKnownNetwork(network); + + doThrow(new RemoteException()).when(mService).forgetKnownNetwork(network); + assertFalse(manager.forgetKnownNetwork(network)); + } + + private void setResources(@Mock Context context) { + when(context.getResources()).thenReturn(mResources); + when(context.getPackageName()).thenReturn(PACKAGE_NAME); + when(mResources.getIdentifier(anyString(), anyString(), anyString())) + .thenReturn(SERVICE_PACKAGE_ID, SERVICE_CLASS_ID); + when(mResources.getString(SERVICE_PACKAGE_ID)).thenReturn(SERVICE_PACKAGE_NAME); + when(mResources.getString(SERVICE_CLASS_ID)).thenReturn(SERVICE_CLASS_NAME); + } + + private TetherNetwork buildTetherNetwork() { + return new TetherNetwork.Builder() + .setDeviceId(DEVICE_ID) + .setDeviceInfo(DEVICE_INFO) + .setNetworkType(NETWORK_TYPE) + .setNetworkName(NETWORK_NAME) + .setHotspotSsid(HOTSPOT_SSID) + .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES) + .build(); + } + + private KnownNetwork buildKnownNetwork() { + return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID) + .setSecurityTypes(SECURITY_TYPES).build(); + } +} diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java new file mode 100644 index 000000000000..3137c7268ae0 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java @@ -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 android.net.wifi.sharedconnectivity.app; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit tests for {@link android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState}. + */ +@SmallTest +public class SharedConnectivitySettingsStateTest { + private static final boolean INSTANT_TETHER_STATE = true; + + private static final boolean INSTANT_TETHER_STATE_1 = false; + /** + * Verifies parcel serialization/deserialization. + */ + @Test + public void testParcelOperation() { + SharedConnectivitySettingsState state = buildSettingsStateBuilder().build(); + + Parcel parcelW = Parcel.obtain(); + state.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + SharedConnectivitySettingsState fromParcel = + SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR); + + assertEquals(state, fromParcel); + assertEquals(state.hashCode(), fromParcel.hashCode()); + } + + /** + * Verifies the Equals operation + */ + @Test + public void testEqualsOperation() { + SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build(); + SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build(); + assertEquals(state1, state2); + + SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder() + .setInstantTetherEnabled(INSTANT_TETHER_STATE_1); + assertNotEquals(state1, builder.build()); + } + + /** + * Verifies the get methods return the expected data. + */ + @Test + public void testGetMethods() { + SharedConnectivitySettingsState state = buildSettingsStateBuilder().build(); + assertEquals(state.isInstantTetherEnabled(), INSTANT_TETHER_STATE); + } + + private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() { + return new SharedConnectivitySettingsState.Builder() + .setInstantTetherEnabled(INSTANT_TETHER_STATE); + } +} diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java new file mode 100644 index 000000000000..b01aec4ad1c1 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java @@ -0,0 +1,139 @@ +/* + * 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 android.net.wifi.sharedconnectivity.app; + +import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP; +import static android.net.wifi.WifiInfo.SECURITY_TYPE_PSK; +import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP; +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE; +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET; +import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR; +import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_WIFI; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetwork}. + */ +@SmallTest +public class TetherNetworkTest { + private static final long DEVICE_ID = 11L; + private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder() + .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL") + .setConnectionStrength(2).setBatteryPercentage(50).build(); + private static final int NETWORK_TYPE = NETWORK_TYPE_CELLULAR; + private static final String NETWORK_NAME = "TEST_NETWORK"; + private static final String HOTSPOT_SSID = "TEST_SSID"; + private static final String HOTSPOT_BSSID = "TEST _BSSID"; + private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP}; + + private static final long DEVICE_ID_1 = 111L; + private static final DeviceInfo DEVICE_INFO_1 = new DeviceInfo.Builder() + .setDeviceType(DEVICE_TYPE_PHONE).setDeviceName("TEST_NAME").setModelName("TEST_MODEL") + .setConnectionStrength(2).setBatteryPercentage(50).build(); + private static final int NETWORK_TYPE_1 = NETWORK_TYPE_WIFI; + private static final String NETWORK_NAME_1 = "TEST_NETWORK1"; + private static final String HOTSPOT_SSID_1 = "TEST_SSID1"; + private static final String HOTSPOT_BSSID_1 = "TEST _BSSID1"; + private static final int[] HOTSPOT_SECURITY_TYPES_1 = {SECURITY_TYPE_PSK, SECURITY_TYPE_EAP}; + + /** + * Verifies parcel serialization/deserialization. + */ + @Test + public void testParcelOperation() { + TetherNetwork network = buildTetherNetworkBuilder().build(); + + Parcel parcelW = Parcel.obtain(); + network.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + TetherNetwork fromParcel = TetherNetwork.CREATOR.createFromParcel(parcelR); + + assertEquals(network, fromParcel); + assertEquals(network.hashCode(), fromParcel.hashCode()); + } + + /** + * Verifies the Equals operation + */ + @Test + public void testEqualsOperation() { + TetherNetwork network1 = buildTetherNetworkBuilder().build(); + TetherNetwork network2 = buildTetherNetworkBuilder().build(); + assertEquals(network1, network2); + + TetherNetwork.Builder builder = buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1); + assertNotEquals(network1, builder.build()); + + builder = buildTetherNetworkBuilder().setDeviceInfo(DEVICE_INFO_1); + assertNotEquals(network1, builder.build()); + + builder = buildTetherNetworkBuilder().setNetworkType(NETWORK_TYPE_1); + assertNotEquals(network1, builder.build()); + + builder = buildTetherNetworkBuilder().setNetworkName(NETWORK_NAME_1); + assertNotEquals(network1, builder.build()); + + builder = buildTetherNetworkBuilder().setHotspotSsid(HOTSPOT_SSID_1); + assertNotEquals(network1, builder.build()); + + builder = buildTetherNetworkBuilder().setHotspotBssid(HOTSPOT_BSSID_1); + assertNotEquals(network1, builder.build()); + + builder = buildTetherNetworkBuilder().setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES_1); + assertNotEquals(network1, builder.build()); + } + + /** + * Verifies the get methods return the expected data. + */ + @Test + public void testGetMethods() { + TetherNetwork network = buildTetherNetworkBuilder().build(); + assertEquals(network.getDeviceId(), DEVICE_ID); + assertEquals(network.getDeviceInfo(), DEVICE_INFO); + assertEquals(network.getNetworkType(), NETWORK_TYPE); + assertEquals(network.getNetworkName(), NETWORK_NAME); + assertEquals(network.getHotspotSsid(), HOTSPOT_SSID); + assertEquals(network.getHotspotBssid(), HOTSPOT_BSSID); + assertArrayEquals(network.getHotspotSecurityTypes(), HOTSPOT_SECURITY_TYPES); + } + + private TetherNetwork.Builder buildTetherNetworkBuilder() { + return new TetherNetwork.Builder() + .setDeviceId(DEVICE_ID) + .setDeviceInfo(DEVICE_INFO) + .setNetworkType(NETWORK_TYPE) + .setNetworkName(NETWORK_NAME) + .setHotspotSsid(HOTSPOT_SSID) + .setHotspotBssid(HOTSPOT_BSSID) + .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES); + } +} diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java new file mode 100644 index 000000000000..e15be8b24a67 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -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 android.net.wifi.sharedconnectivity.service; + +import static org.junit.Assert.assertNotNull; + +import android.content.Intent; +import android.net.wifi.sharedconnectivity.app.KnownNetwork; +import android.net.wifi.sharedconnectivity.app.TetherNetwork; +import android.os.Handler; +import android.os.test.TestLooper; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit tests for {@link android.net.wifi.sharedconnectivity.service.SharedConnectivityService}. + */ +@SmallTest +public class SharedConnectivityServiceTest { + + /** + * Verifies service returns + */ + @Test + public void testOnBind() { + SharedConnectivityService service = createService(); + assertNotNull(service.onBind(new Intent())); + } + + @Test + public void testCallbacks() { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + } + + private SharedConnectivityService createService() { + return new SharedConnectivityService(new Handler(new TestLooper().getLooper())) { + @Override + public void onConnectTetherNetwork(TetherNetwork network) {} + + @Override + public void onDisconnectTetherNetwork() {} + + @Override + public void onConnectKnownNetwork(KnownNetwork network) {} + + @Override + public void onForgetKnownNetwork(KnownNetwork network) {} + }; + } +} |