Merge "Start ODP system service."
diff --git a/core/api/current.txt b/core/api/current.txt
index 9851428..de540bf 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4757,6 +4757,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 d0c000e..4a0b2eb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9783,6 +9783,133 @@
}
+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 2f5e820..b3e3f35 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1863,6 +1863,14 @@
}
+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 b57fb20..3b88257 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -343,6 +343,13 @@
"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 @@
/** 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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}
*/
- public boolean getIgnorePendingIntentCreatorForegroundState() {
- return mIgnorePendingIntentCreatorForegroundState;
+ @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 @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
+ return mPendingIntentCreatorBackgroundActivityStartMode;
}
/**
@@ -2240,6 +2282,10 @@
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 @@
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 73f34eb..7d40a22 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1432,14 +1432,14 @@
.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 @@
"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 @@
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 20d19c1..5ef29e4 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -388,7 +388,6 @@
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 d8ec7cc..b494a20 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -39,6 +39,7 @@
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 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
* 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 @@
* @see #setDelegatedScopes
* @see #DELEGATION_BLOCK_UNINSTALL
*/
+ @SupportsCoexistence
public void setUninstallBlocked(@Nullable ComponentName admin, String packageName,
boolean uninstallBlocked) {
throwIfParentInstance("setUninstallBlocked");
@@ -12755,6 +12762,7 @@
* @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 @@
* @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 6e49e95..b7ec7b5 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -545,6 +545,7 @@
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 335975b..4515663 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1107,7 +1107,7 @@
} 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 8587348..7844b40 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
@@ -39,5 +39,15 @@
* {@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 0efd264..1df45d1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1069,11 +1069,6 @@
* 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 c80c57c..e5b76f6 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 1c032ee..b29efab 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11468,6 +11468,13 @@
"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 0f7c9b6..eb5e893 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -117,6 +117,12 @@
*/
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 @@
// 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 @@
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 @@
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 @@
| 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 @@
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 @@
| 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 @@
};
@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 70a7739..ba7d823 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -781,19 +781,6 @@
}
}
- 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 7d1dc76..c8c910d 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -28,6 +28,7 @@
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 @@
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 @@
}
@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 @@
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 @@
} 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 @@
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 @@
} 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 @@
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 @@
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 @@
} 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 681bc7a..1eecb41 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -202,12 +202,12 @@
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 0ea60a7..18c8eb4 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -447,13 +447,13 @@
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 1c85ca2..b529a10 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -214,11 +214,11 @@
* 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 @@
* 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 54221ce..4f827cd 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 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 @@
dest.writeInt(mBehavior);
dest.writeInt(mRequestedVisibleTypes);
dest.writeString(mPackageName);
- dest.writeIntArray(mTransientBarTypes);
+ dest.writeInt(mTransientBarTypes);
dest.writeParcelableArray(mLetterboxDetails, flags);
}
@@ -113,7 +111,7 @@
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 e56c381..5c3759f 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 @@
@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 2f51479..4d820ac 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -681,7 +681,7 @@
* @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 92a9e56..82de7b8 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 f460219a..aeb46cc 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 0000000..d8298fd
--- /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 048c48b..f79ba28 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 @@
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 f2d6250..53f4747 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 0000000..1ff1694
--- /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 0000000..141a6ad
--- /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 0000000..0b1423a
--- /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 20602a1..274dcae 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.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 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 @@
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 mDeviceStateRequest;
+ private DeviceStateRequest mRearDisplayStateRequest;
+ @GuardedBy("mLock")
+ private RearDisplayPresentationController mRearDisplayPresentationController;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private DisplayMetrics mRearDisplayMetrics;
+
+ @WindowAreaSessionState
+ @GuardedBy("mLock")
+ 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 @@
* 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 @@
/**
* 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 @@
* @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 @@
/**
* 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,13 +222,176 @@
}
}
+ /**
+ * 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 removeRearDisplayPresentationStatusListener(
+ @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
+ synchronized (mLock) {
+ 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 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 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 ExtensionWindowAreaPresentation getRearDisplayPresentation() {
+ synchronized (mLock) {
+ ExtensionWindowAreaPresentation presentation = null;
+ if (mRearDisplayPresentationController != null) {
+ presentation = mRearDisplayPresentationController.getWindowAreaPresentation();
+ }
+ return presentation;
+ }
+ }
+
+ @Override
+ public void onSupportedStatesChanged(int[] supportedStates) {
+ synchronized (mLock) {
+ mCurrentSupportedDeviceStates = supportedStates;
+ updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
+ updateRearDisplayPresentationStatusListeners(
+ getCurrentRearDisplayPresentationModeStatus());
}
}
@@ -182,34 +399,17 @@
public void onStateChanged(int state) {
synchronized (mLock) {
mCurrentDeviceState = state;
- updateStatusConsumers(getCurrentStatus());
+ updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
+ updateRearDisplayPresentationStatusListeners(
+ getCurrentRearDisplayPresentationModeStatus());
}
}
- @Override
- public void addRearDisplayPresentationStatusListener(
- @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
-
- @Override
- public void removeRearDisplayPresentationStatusListener(
- @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
-
- @Override
- public void startRearDisplayPresentationSession(@NonNull Activity activity,
- @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {}
-
- @Override
- public void endRearDisplayPresentationSession() {}
-
- @Override
- @Nullable
- public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
- return null;
- }
@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 @@
/**
* 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 @@
}
}
+ @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 @@
@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
index 378ad81..7cd5dd6 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
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 5d451a5..7a6aec7 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 void onLocationChanged() {
getBoundsOnScreen(mTmpRect);
- mTaskViewTaskController.onLocationChanged(mTmpRect);
+ mTaskViewTaskController.setWindowBounds(mTmpRect);
}
/**
@@ -198,7 +198,7 @@
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 69ce35f..1f223a6 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
.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 e36dfc3..36c0cb6 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 @@
}
}
+ /** 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 @@
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 @@
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 @@
for (String key : mSuppressedGroupToNotifKeys.keySet()) {
pw.println(" suppressing: " + key);
}
+
+ pw.print("mAppBubbleTaskId: " + mAppBubbleTaskId);
}
}
@@ -1773,8 +1791,7 @@
@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 1feff18..57c7731 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 @@
// 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 45b234a..f616e6f 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 @@
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 0000000..042721c9
--- /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 6728c00..d9ac76e 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.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 @@
}
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 @@
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 @@
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 283b1ec..480bf93 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 @@
* 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 @@
/** 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 @@
}
@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 @@
/** 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 @@
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 @@
}
@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 @@
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 8ba2583..aad27b9 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.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 @@
// 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 83158ff..d9d1009 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.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 @@
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 21eeaa2..7cb5cf2 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.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.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.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 @@
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 @@
@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) {
- }
- };
- 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.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();
+ }
+ }
+
+ mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
+ activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId);
}
/**
@@ -530,8 +541,10 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 39cf5f1..5a9170b 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.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.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.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 @@
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 @@
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 @@
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 @@
@SplitPosition
int getMainStagePosition() {
- return SplitLayout.reversePosition(mSideStagePosition);
+ return reverseSplitPosition(mSideStagePosition);
}
int getTaskId(@SplitPosition int splitPosition) {
@@ -950,7 +1018,7 @@
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 @@
}
}
- 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 @@
// 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 @@
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 b4e0584..02f19eb 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 @@
@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 44d6a0d..b2f61c2 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_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 @@
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 ea3af9d..d0e2601 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_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 @@
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 @@
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 28496f1..c5202dc 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -108,133 +108,148 @@
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
- * @hide
+ */
+ public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003;
+
+ /**
+ * Indicates the route is a group of devices.
+ *
+ * @see #getType
*/
public static final int TYPE_GROUP = 2000;
@@ -436,16 +451,23 @@
}
/**
- * 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 @@
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 @@
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 @@
/**
* 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 ab78536..6caedda 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -259,7 +259,7 @@
@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 @@
* 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 @@
* 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 ed44508..230d763 100644
--- a/media/java/android/media/tv/AdBuffer.java
+++ b/media/java/android/media/tv/AdBuffer.java
@@ -24,9 +24,8 @@
/**
* 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 @@
/**
* Gets corresponding AD request ID.
+ *
+ * @return The ID of the ad request
*/
public int getId() {
return mId;
@@ -67,6 +68,8 @@
/**
* Gets the mime type of the data.
+ *
+ * @return The mime type of the data.
*/
@NonNull
public String getMimeType() {
@@ -74,7 +77,17 @@
}
/**
- * 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 @@
}
/**
- * 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 60dfc5e..d8cddfc 100644
--- a/media/java/android/media/tv/AdRequest.java
+++ b/media/java/android/media/tv/AdRequest.java
@@ -79,7 +79,6 @@
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 @@
*
* @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 a15e8c1..7ec4eb2 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -43,7 +43,6 @@
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 7a4d988..8166114 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1005,9 +1005,10 @@
/**
* 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 @@
}
/**
- * 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 cdaa3e5..8b85fa1 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -896,10 +896,10 @@
/**
* 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 @@
/**
* 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 ab2d815..6c463e1 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 7397688..b842761 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 c5ed5c9..918f9c6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -546,17 +546,16 @@
}
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 @@
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 6f5f4fe..e3fd354 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -88,7 +88,7 @@
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 d6909719..49ac482 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 c66ea5e..6ea1d8d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -64,7 +64,7 @@
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 d8420cd..15acd8c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -198,7 +198,7 @@
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 @@
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 @@
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 d0271ab..984057a 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 @@
) {
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 f8d008e..0b9e578 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.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.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.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 @@
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 c35fb3b..4b4cfb7 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -30,5 +30,6 @@
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 4b73e94..b92729d 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.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 @@
}
}
}
-
-/**
- * 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 0000000..67f9ea5
--- /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 14dc785..429f97b 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 @@
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 @@
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 @@
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 c810648..724588f 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 @@
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 32b283e..62189dc 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.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 @@
Icon(
imageVector = Icons.Outlined.ArrowBack,
contentDescription = contentDescription,
+ modifier = Modifier.autoMirrored(),
)
}
}
@@ -75,3 +81,10 @@
)
}
}
+
+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 730aa8f..379b9a7 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 @@
@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 6fb5555..c036fdb 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 @@
}
@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 d222b98..77e514f 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 @@
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 @@
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 @@
}
}
+ // 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 @@
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 50f3713..d242198 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_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 @@
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 @@
*
* @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 @@
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 de16d4a..1c82be9 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 @@
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 @@
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 31038cd..04c1c31 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 @@
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 293a590..bf9e428 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 @@
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 31eb009..f0bc1df 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 @@
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 3eec565..e55ac1b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -106,6 +106,12 @@
updateDoubleLineClock();
}
};
+ private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean change) {
+ setWeatherVisibility();
+ }
+ };
private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
mKeyguardUnlockAnimationListener =
@@ -216,7 +222,15 @@
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 @@
}
}
+ 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 fb0c0a6..5ca36ab 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 @@
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 57c99d1..d02eee0 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 @@
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 @@
@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 @@
// 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 @@
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 @@
// 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 35423f4..d5d7325 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_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.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.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 @@
}
@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 @@
}
@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 f3712e6..c3d7369 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.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 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.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.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 @@
}
@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 @@
}
@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 335172e..30d2d7a 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 @@
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 6bfe1a0..be615d6 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.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 @@
@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 @@
*
* 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 @@
// 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 @@
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 @@
)
}
+ /** 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 0000000..bd822d4
--- /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 d5f4a5a..d40bf2b 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 @@
private val noteTaskController: NoteTaskController,
private val commandQueue: CommandQueue,
@NoteTaskEnabledKey private val isEnabled: Boolean,
+ private val optionalKeyguardManager: Optional<KeyguardManager>,
) {
@VisibleForTesting
@@ -37,11 +40,21 @@
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 @@
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 11dc1d7..0000000
--- 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 ec6a16a..b8800a2 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 @@
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 cfbaa48..43869cc 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.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 @@
}
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 f203e7a..3ac5bfa 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.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 @@
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 @@
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 c573080..f53f824 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 @@
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 @@
@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 @@
}
// 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 3aaad87..2cf1f53 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.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 @@
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 @@
}
@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 @@
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 fe76c7d..81c7197 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.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 @@
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 856d7de..fecaa3a 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.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 @@
}
@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 @@
}
@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 e595ddf..1966a66 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_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.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 @@
// 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 e5ab473..5cf01af 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 @@
/** 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 @@
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 @@
// 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 a374885..0a07439 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 @@
}
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 @@
// 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 36b3f89..ccc4e4a 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 @@
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 @@
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 509d5f0..70ba306 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 @@
@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 @@
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 @@
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 @@
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 @@
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 8440455..39c4e06 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.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.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 @@
@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 @@
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,
+ )
- verify(bubbles).showOrHideAppBubble(notesIntent)
- verify(context, never()).startActivity(notesIntent)
+ 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_isInMultiWindowMode_shouldStartActivity() {
+ fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
- verify(context).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ 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_shouldStartActivityAndLogUiEvent() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = true,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
+ )
+
+ 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 @@
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 0000000..d6495d8
--- /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 010ac5b..53720ff 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.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
/**
@@ -55,12 +59,16 @@
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 @@
// 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 18be92b..0000000
--- 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 a1d42a0..cdc683f 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.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 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(noteTaskController.showNoteTask()).then {}
}
private fun createUnderTest(isEnabled: Boolean) =
@@ -96,6 +95,7 @@
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 b1ca1c0..f581154 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.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 @@
@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 @@
@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 @@
@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 @@
@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 ccf378a..9312643 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 @@
@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 @@
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 fb781e8..cc23485 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 @@
}
@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 @@
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 1a8e244..53bb340 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.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 @@
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 cc8aec7..a4fd52d 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 04c0d64..2b2de81e 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 @@
return runList;
}
- @GuardedBy(anyOf = {"mService", "mProfileLock"})
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
int getLruSizeLOSP() {
return mLruProcesses.size();
}
@@ -3896,7 +3896,7 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
* 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 @@
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 12784bf..60a7f93 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 @@
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 969a174..0b5c1c1 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 @@
}
/**
+ * 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 a90679e..932c0b4 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 @@
@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 513b3e3..cf54662 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 @@
}
});
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 @@
}
@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 29caefb..91ef167 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 @@
/**
* 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 @@
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 @@
// 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 @@
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 @@
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 @@
@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 @@
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 6090386..1086c55 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 56c9056..c695862 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 @@
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 13fcff3..e3d92a7 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 @@
// 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 14b9121..740d2a3 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 @@
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 8ee3a72..a113d01 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 @@
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 @@
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 0c99e86..b4fc195 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.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 @@
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 c5bcddc..6b9be25 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 @@
// 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 8beb0b6..002585f 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 @@
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 a9edce1..3cbaebe 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 @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 @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 fe8a500..d5cc7ca 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.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.utils.Slogf;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -80,11 +77,11 @@
*/
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 @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 @@
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}.
+ * Mapping from each started user to its profile group.
*/
@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.
- */
- @GuardedBy("mLock")
- @Nullable
- private final List<Integer> mStartedInvisibleProfileUserIds;
+ private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
/**
* Handler user to call listeners
@@ -180,14 +164,9 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@GuardedBy("mLock")
private void unassignUserFromAllDisplaysOnStopLocked(@UserIdInt int userId) {
if (DBG) {
- Slogf.d(TAG, "Removing %d from mStartedVisibleProfileGroupIds (%s)", userId,
- mStartedVisibleProfileGroupIds);
+ Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
+ mStartedProfileGroupIds);
}
- mStartedVisibleProfileGroupIds.delete(userId);
- if (mStartedInvisibleProfileUserIds != null) {
- Slogf.d(TAG, "Removing %d from list of invisible profiles", userId);
- mStartedInvisibleProfileUserIds.remove(Integer.valueOf(userId));
- }
+ mStartedProfileGroupIds.delete(userId);
if (!mVisibleBackgroundUsersEnabled) {
// Don't need to update mUsersAssignedToDisplayOnStart because methods (such as
@@ -625,8 +575,7 @@
* 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 @@
}
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 @@
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 @@
}
synchronized (mLock) {
- int profileGroupId;
- synchronized (mLock) {
- profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+ if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) {
+ // User assigned to display on start
+ return true;
}
- if (isProfile(userId, profileGroupId)) {
- return isFullUserVisibleOnBackgroundLocked(profileGroupId, displayId);
- }
- 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 @@
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 @@
// 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 @@
}
}
- // 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 @@
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 @@
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 @@
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 2fbf3fb..751f535 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 @@
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 @@
@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 5521384..ec052ec 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.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 @@
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 83f4805..4489ba9 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.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 @@
}
@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 @@
}
@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 @@
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 @@
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 @@
// 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 65127e4..8ce8c46 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 @@
// 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 @@
}
}
+ 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 50eb356..c37a3d7 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 @@
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 cd79f2e..e1fdeca1 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.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 @@
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 d93a62d..9b643c5 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.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.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.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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 e87680a..3c4d706 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 @@
/** 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 @@
private boolean mIsFreeformWindowOverlappingWithNavBar;
- private boolean mLastImmersiveMode;
+ private boolean mIsImmersiveMode;
// The windows we were told about in focusChanged.
private WindowState mFocusedWindow;
@@ -1077,8 +1076,16 @@
} 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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
}
}
- // 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 @@
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 bd82113..97dd291 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.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.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.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 @@
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 @@
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 @@
};
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 @@
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 @@
}
void hideTransient() {
- if (mShowingTransientTypes.size() == 0) {
+ if (mShowingTransientTypes == 0) {
return;
}
@@ -235,20 +241,25 @@
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 @@
? 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 @@
}
}
- 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 @@
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 @@
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 @@
* @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 @@
* 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;
}
- mShowingTransientTypes.clear();
+ final StatusBarManagerInternal statusBarManager = mPolicy.getStatusBarManagerInternal();
+ if (statusBarManager != null) {
+ statusBarManager.abortTransient(mDisplayContent.getDisplayId(), mShowingTransientTypes);
+ }
+ mShowingTransientTypes = 0;
+ mDisplayContent.setLayoutNeeded();
+ mDisplayContent.mWmService.requestTraversal();
dispatchTransientSystemBarsVisibilityChanged(
mFocusedWin,
@@ -488,7 +506,7 @@
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 @@
// 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 @@
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 @@
@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 f5af292..2b7a451 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 @@
// 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 @@
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 @@
}
/**
- * @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 @@
}
/**
- * @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 @@
return mControlTarget;
}
+ InsetsControlTarget getFakeControlTarget() {
+ return mFakeControlTarget;
+ }
+
boolean isClientVisible() {
return mClientVisible;
}
@@ -609,15 +606,15 @@
}
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 a3f62b2..0e1e63e 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 @@
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.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 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 @@
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 @@
}
};
- 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 @@
}
@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 @@
}
}
- 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 @@
// 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 @@
@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 (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 */);
+
+ // Get control target again in case the provider didn't accept the one we passed to it.
+ target = provider.getControlTarget();
+ if (target == lastTarget) {
+ return;
+ }
+ }
+ if (lastTarget != null) {
+ removeFromControlMaps(lastTarget, provider, fake);
+ mPendingControlChanged.add(lastTarget);
}
if (target != null) {
- addToControlMaps(target, type, false /* fake */);
+ addToControlMaps(target, provider, fake);
mPendingControlChanged.add(target);
}
}
- /**
- * 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;
- }
- provider.updateControlForFakeTarget(fakeTarget);
- if (previous != null) {
- removeFromControlMaps(previous, type, true /* fake */);
- mPendingControlChanged.add(previous);
- }
- if (fakeTarget != null) {
- addToControlMaps(fakeTarget, type, true /* fake */);
- mPendingControlChanged.add(fakeTarget);
- }
- }
-
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 @@
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 @@
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 2343906..110cce2 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 @@
}
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 98ca9ae..5860776 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 @@
.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 ee11f68..1e53cc3 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.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.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 @@
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 f0c099f..e82dc82 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 @@
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 @@
* 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 @@
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 @@
} 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 @@
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 18788bf..2c23b5d 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 @@
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 @@
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 b06bdb1..7173980 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 @@
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 @@
* {@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 @@
}
}
- 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 @@
}
}
- 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 b46a720..06a8869 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.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 @@
// 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 2e9c9cf..73e417e 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 @@
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 7b880fe..e8aa38e 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 @@
}
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 @@
}
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 @@
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 4cb7a8f..dd757bc 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 @@
{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 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 @@
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 @@
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 be60946..447c67f 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.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 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 @@
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 @@
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 351afb9..2345e3f 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.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 @@
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 @@
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 @@
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 fbdcc44..3d504ef 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 @@
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();
}
- // 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. */
- public static CredentialDescriptionRegistry forUser(int userId) {
- CredentialDescriptionRegistry session =
- sCredentialDescriptionSessionPerUser.get(userId, null);
+ /** Represents the results of a given query into the registry. */
+ public static final class FilterResult {
+ final String mPackageName;
+ final List<CredentialEntry> mCredentialEntries;
- if (session == null) {
- session = new CredentialDescriptionRegistry();
- sCredentialDescriptionSessionPerUser.put(userId, session);
+ private FilterResult(String packageName,
+ List<CredentialEntry> credentialEntries) {
+ mPackageName = packageName;
+ mCredentialEntries = credentialEntries;
}
- return session;
+ }
+
+ /** Get and/or create a {@link CredentialDescription} for the given user id. */
+ @GuardedBy("sLock")
+ public static CredentialDescriptionRegistry forUser(int userId) {
+ sLock.lock();
+ try {
+ CredentialDescriptionRegistry session =
+ sCredentialDescriptionSessionPerUser.get(userId, null);
+
+ if (session == null) {
+ session = new CredentialDescriptionRegistry();
+ sCredentialDescriptionSessionPerUser.put(userId, session);
+ }
+ return session;
+ } finally {
+ sLock.unlock();
+ }
+ }
+
+ /** 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();
+ }
}
private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
@@ -74,7 +111,7 @@
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 @@
}
}
+ /** Returns package names and entries of a CredentialProviders that can satisfy a given
+ * {@link CredentialDescription}. */
+ 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> filterCredentials(String flatRequestString) {
-
+ 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 ff72ed7..ea63c30 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.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 @@
}
-
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
int resolvedUserId) {
@@ -236,6 +233,7 @@
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 @@
@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 @@
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 @@
callingUid,
callback,
request,
- constructCallingAppInfo(callingPackage, userId));
+ constructCallingAppInfo(callingPackage, userId),
+ CancellationSignal.fromTransport(cancelTransport));
- // Initiate all provider sessions
- List<ProviderSession> providerSessions =
- initiateProviderSessions(
- session,
- request.getCredentialOptions().stream()
- .map(CredentialOption::getType)
- .collect(Collectors.toList()));
+ 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 @@
+ e.getMessage());
}
}
-
- // Iterate over all provider sessions and invoke the request
providerSessions.forEach(ProviderSession::invokeSession);
+
return cancelTransport;
}
@@ -385,7 +428,8 @@
callingUid,
request,
callback,
- constructCallingAppInfo(callingPackage, userId));
+ constructCallingAppInfo(callingPackage, userId),
+ CancellationSignal.fromTransport(cancelTransport));
// Initiate all provider sessions
List<ProviderSession> providerSessions =
@@ -405,8 +449,7 @@
}
// Iterate over all provider sessions and invoke the request
- providerSessions.forEach(
- ProviderSession::invokeSession);
+ providerSessions.forEach(ProviderSession::invokeSession);
return cancelTransport;
}
@@ -497,7 +540,8 @@
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 @@
}
// 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 e3a27ec..e732c23 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.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 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 @@
}
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 @@
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 b112649..b20f0cd 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 @@
@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 cc5a8ab..ade40ad 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 @@
// 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 @@
@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 dec3432..3ccead1 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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 0000000..461f447
--- /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 8e0d6f8..d6f97ff 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.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 @@
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 @@
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 @@
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 @@
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 8cad6ac..2dea8bd 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 @@
* @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 @@
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 @@
* @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 @@
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 @@
* @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 @@
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 f92ffe2..9f1bd8f 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.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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
}
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 f412dd3..cae6c39 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -38,6 +38,7 @@
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 @@
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 02da25d..59551a3 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 @@
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 @@
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 @@
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 8979585..38cf634 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 @@
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 @@
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 @@
listener.verify();
}
+ /* TODO: re-add
@Test
public void
@@ -226,4 +227,5 @@
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 566084a..5176d68 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.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 @@
}
@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 @@
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 @@
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 @@
}
@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 @@
* 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 f084063..49c6a88 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 @@
}
@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 @@
}
@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 99f7905..e605a31 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 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 @@
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 26524d7..a40d3fe 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.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 @@
@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 27d912b..0eff0da 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 @@
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 ac1dca9..488c533 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 @@
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 1d70fc6..d28050d 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 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 @@
private LogicalDisplay mLogicalDisplay;
private DisplayDevice mDisplayDevice;
+ private DisplayDeviceRepository mDeviceRepo;
private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
@Before
@@ -66,7 +70,7 @@
// 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 @@
@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 @@
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 d4ab794..ebd63a0 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 @@
@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 @@
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 cff3bf8..b27ba88 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 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 @@
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 @@
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 56d59b4..e5fe32a 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 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 @@
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 @@
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 45cf530..6656f4c 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 @@
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 @@
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 dba2995..1a126cf 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 @@
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 @@
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 @@
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 @@
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 88ecd3f..74fde65 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_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 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 @@
@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 @@
// 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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
// 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 @@
@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 @@
@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 @@
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 @@
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 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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 42bbd2d..94193f4 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 @@
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 1e5ec4c..7d13de8 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 @@
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 c44869b..17b44ee 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 @@
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 @@
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 bd63560..714eb4b 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.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 @@
@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 7c86a75a..20564d6 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -951,6 +951,23 @@
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 51ecfb0..836cb53 100644
--- a/telephony/java/android/telephony/CallState.java
+++ b/telephony/java/android/telephony/CallState.java
@@ -285,12 +285,12 @@
/**
* 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 c238f24..819b0b2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14894,7 +14894,10 @@
/**
* 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 @@
/**
* 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 1ea7fdc..d07edeb 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -27,6 +27,7 @@
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 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 225e750..434226d 100644
--- a/wifi/java/Android.bp
+++ b/wifi/java/Android.bp
@@ -27,7 +27,10 @@
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 0000000..35d5c15
--- /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 0000000..7874b2a
--- /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 0000000..140d72a
--- /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 0000000..34b7e94
--- /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 0000000..dcb5201
--- /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 0000000..b43e4f7
--- /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 0000000..289afac
--- /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 0000000..dd2fa94
--- /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 0000000..6cc4cfe
--- /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 0000000..bbdad53
--- /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 0000000..6e56138
--- /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 0000000..5d79405
--- /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 0000000..234319a
--- /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 0000000..f8f0700
--- /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 0000000..266afcc
--- /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 0000000..9aeccac
--- /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 0000000..3137c72
--- /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 0000000..b01aec4
--- /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 0000000..e15be8b
--- /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) {}
+ };
+ }
+}