diff options
206 files changed, 6414 insertions, 2335 deletions
diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java index 8ee9616411b8..ef5552e70e6b 100644 --- a/apex/media/framework/java/android/media/MediaCommunicationManager.java +++ b/apex/media/framework/java/android/media/MediaCommunicationManager.java @@ -36,6 +36,8 @@ import android.view.KeyEvent; import androidx.annotation.RequiresApi; +import androidx.annotation.RequiresApi; + import com.android.internal.annotations.GuardedBy; import com.android.modules.annotation.MinSdk; import com.android.modules.utils.build.SdkLevel; diff --git a/core/api/current.txt b/core/api/current.txt index b52a3e45c3eb..d7d465f01b98 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -61,6 +61,7 @@ package android { field public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN"; field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS"; + field public static final String BODY_SENSORS_BACKGROUND = "android.permission.BODY_SENSORS_BACKGROUND"; field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED"; field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS"; field public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY"; @@ -21932,6 +21933,7 @@ package android.media { public class MediaActionSound { ctor public MediaActionSound(); method public void load(int); + method public static boolean mustPlayShutterSound(); method public void play(int); method public void release(); field public static final int FOCUS_COMPLETE = 1; // 0x1 @@ -43507,6 +43509,7 @@ package android.telephony { field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1 field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3 field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0 + field public static final int DATA_HANDOVER_IN_PROGRESS = 5; // 0x5 field public static final int DATA_SUSPENDED = 3; // 0x3 field public static final int DATA_UNKNOWN = -1; // 0xffffffff field public static final int DEFAULT_PORT_INDEX = 0; // 0x0 @@ -43756,6 +43759,8 @@ package android.telephony.data { method public String getMmsProxyAddressAsString(); method public int getMmsProxyPort(); method public android.net.Uri getMmsc(); + method public int getMtuV4(); + method public int getMtuV6(); method public int getMvnoType(); method public int getNetworkTypeBitmask(); method public String getOperatorNumeric(); @@ -43812,10 +43817,14 @@ package android.telephony.data { method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyAddress(@Nullable String); method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyPort(int); method @NonNull public android.telephony.data.ApnSetting.Builder setMmsc(@Nullable android.net.Uri); + method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV4(int); + method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV6(int); method @NonNull public android.telephony.data.ApnSetting.Builder setMvnoType(int); method @NonNull public android.telephony.data.ApnSetting.Builder setNetworkTypeBitmask(int); method @NonNull public android.telephony.data.ApnSetting.Builder setOperatorNumeric(@Nullable String); method @NonNull public android.telephony.data.ApnSetting.Builder setPassword(@Nullable String); + method @NonNull public android.telephony.data.ApnSetting.Builder setPersistent(boolean); + method @NonNull public android.telephony.data.ApnSetting.Builder setProfileId(int); method @NonNull public android.telephony.data.ApnSetting.Builder setProtocol(int); method @Deprecated public android.telephony.data.ApnSetting.Builder setProxyAddress(java.net.InetAddress); method @NonNull public android.telephony.data.ApnSetting.Builder setProxyAddress(@Nullable String); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index a51d2b165516..850b2e6433a2 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -138,6 +138,8 @@ package android.media { public class AudioManager { method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); + method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp(); + method @NonNull public java.util.List<android.bluetooth.BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio(); method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BtProfileConnectionInfo); method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setA2dpSuspended(boolean); method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setBluetoothHeadsetProperties(@NonNull String, boolean, boolean); @@ -227,6 +229,10 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR; } + public final class IpSecManager { + field public static final int DIRECTION_FWD = 2; // 0x2 + } + public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { method public int getResourceId(); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 6e1ab915b92c..15ed54d8726e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -240,6 +240,7 @@ package android { field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final String READ_PROJECTION_STATE = "android.permission.READ_PROJECTION_STATE"; field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES"; + field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS"; field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES"; field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO"; field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; @@ -344,6 +345,7 @@ package android { public static final class R.attr { field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600 + field public static final int gameSessionService; field public static final int hotwordDetectionService = 16844326; // 0x1010626 field public static final int isVrOnly = 16844152; // 0x1010578 field public static final int minExtensionVersion = 16844305; // 0x1010611 @@ -1398,7 +1400,9 @@ package android.app.compat { method public static boolean isChangeEnabled(long); method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, @NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(allOf={"android.permission.READ_COMPAT_CHANGE_CONFIG", "android.permission.LOG_COMPAT_CHANGE"}) public static boolean isChangeEnabled(long, int); + method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void putAllPackageOverrides(@NonNull java.util.Map<java.lang.String,java.util.Map<java.lang.Long,android.app.compat.PackageOverride>>); method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void putPackageOverrides(@NonNull String, @NonNull java.util.Map<java.lang.Long,android.app.compat.PackageOverride>); + method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void removeAllPackageOverrides(@NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.Long>>); method @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public static void removePackageOverrides(@NonNull String, @NonNull java.util.Set<java.lang.Long>); } @@ -7605,6 +7609,7 @@ package android.media.tv.tuner.frontend { method public int getBer(); method @NonNull public int[] getBers(); method @NonNull public int[] getCodeRates(); + method @NonNull public int[] getDvbtCellIds(); method @NonNull public int[] getExtendedModulations(); method @Deprecated public int getFreqOffset(); method public long getFreqOffsetLong(); @@ -7647,6 +7652,7 @@ package android.media.tv.tuner.frontend { field public static final int FRONTEND_STATUS_TYPE_BERS = 23; // 0x17 field public static final int FRONTEND_STATUS_TYPE_CODERATES = 24; // 0x18 field public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = 0; // 0x0 + field public static final int FRONTEND_STATUS_TYPE_DVBT_CELL_IDS = 40; // 0x28 field public static final int FRONTEND_STATUS_TYPE_EWBS = 13; // 0xd field public static final int FRONTEND_STATUS_TYPE_FEC = 8; // 0x8 field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 18; // 0x12 @@ -7881,6 +7887,7 @@ package android.media.tv.tuner.frontend { method public void onAtsc3PlpInfosReported(@NonNull android.media.tv.tuner.frontend.Atsc3PlpInfo[]); method public default void onDvbcAnnexReported(int); method public void onDvbsStandardReported(int); + method public default void onDvbtCellIdsReported(@NonNull int[]); method public void onDvbtStandardReported(int); method public default void onFrequenciesLongReported(@NonNull long[]); method @Deprecated public void onFrequenciesReported(@NonNull int[]); @@ -10736,12 +10743,35 @@ package android.service.euicc { package android.service.games { + public final class CreateGameSessionRequest implements android.os.Parcelable { + ctor public CreateGameSessionRequest(int, @NonNull String); + method public int describeContents(); + method @NonNull public String getGamePackageName(); + method public int getTaskId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.games.CreateGameSessionRequest> CREATOR; + } + public class GameService extends android.app.Service { ctor public GameService(); method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); method public void onConnected(); method public void onDisconnected(); - field public static final String SERVICE_INTERFACE = "android.service.games.GameService"; + field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE"; + field public static final String SERVICE_META_DATA = "android.game_service"; + } + + public abstract class GameSession { + ctor public GameSession(); + method public void onCreate(); + method public void onDestroy(); + } + + public abstract class GameSessionService extends android.app.Service { + ctor public GameSessionService(); + method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method @NonNull public abstract android.service.games.GameSession onNewSession(@NonNull android.service.games.CreateGameSessionRequest); + field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE"; } } @@ -13086,21 +13116,23 @@ package android.telephony.data { public final class DataProfile implements android.os.Parcelable { method public int describeContents(); - method @NonNull public String getApn(); - method public int getAuthType(); - method public int getBearerBitmask(); + method @Deprecated @NonNull public String getApn(); + method @Nullable public android.telephony.data.ApnSetting getApnSetting(); + method @Deprecated public int getAuthType(); + method @Deprecated public int getBearerBitmask(); method @Deprecated public int getMtu(); - method public int getMtuV4(); - method public int getMtuV6(); - method @Nullable public String getPassword(); - method public int getProfileId(); - method public int getProtocolType(); - method public int getRoamingProtocolType(); - method public int getSupportedApnTypesBitmask(); + method @Deprecated public int getMtuV4(); + method @Deprecated public int getMtuV6(); + method @Deprecated @Nullable public String getPassword(); + method @Deprecated public int getProfileId(); + method @Deprecated public int getProtocolType(); + method @Deprecated public int getRoamingProtocolType(); + method @Deprecated public int getSupportedApnTypesBitmask(); + method @Nullable public android.telephony.data.TrafficDescriptor getTrafficDescriptor(); method public int getType(); - method @Nullable public String getUserName(); + method @Deprecated @Nullable public String getUserName(); method public boolean isEnabled(); - method public boolean isPersistent(); + method @Deprecated public boolean isPersistent(); method public boolean isPreferred(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataProfile> CREATOR; @@ -13113,21 +13145,23 @@ package android.telephony.data { ctor public DataProfile.Builder(); method @NonNull public android.telephony.data.DataProfile build(); method @NonNull public android.telephony.data.DataProfile.Builder enable(boolean); - method @NonNull public android.telephony.data.DataProfile.Builder setApn(@NonNull String); - method @NonNull public android.telephony.data.DataProfile.Builder setAuthType(int); - method @NonNull public android.telephony.data.DataProfile.Builder setBearerBitmask(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setApn(@NonNull String); + method @NonNull public android.telephony.data.DataProfile.Builder setApnSetting(@NonNull android.telephony.data.ApnSetting); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setAuthType(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setBearerBitmask(int); method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setMtu(int); - method @NonNull public android.telephony.data.DataProfile.Builder setMtuV4(int); - method @NonNull public android.telephony.data.DataProfile.Builder setMtuV6(int); - method @NonNull public android.telephony.data.DataProfile.Builder setPassword(@NonNull String); - method @NonNull public android.telephony.data.DataProfile.Builder setPersistent(boolean); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setMtuV4(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setMtuV6(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setPassword(@NonNull String); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setPersistent(boolean); method @NonNull public android.telephony.data.DataProfile.Builder setPreferred(boolean); - method @NonNull public android.telephony.data.DataProfile.Builder setProfileId(int); - method @NonNull public android.telephony.data.DataProfile.Builder setProtocolType(int); - method @NonNull public android.telephony.data.DataProfile.Builder setRoamingProtocolType(int); - method @NonNull public android.telephony.data.DataProfile.Builder setSupportedApnTypesBitmask(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setProfileId(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setProtocolType(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setRoamingProtocolType(int); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setSupportedApnTypesBitmask(int); + method @NonNull public android.telephony.data.DataProfile.Builder setTrafficDescriptor(@NonNull android.telephony.data.TrafficDescriptor); method @NonNull public android.telephony.data.DataProfile.Builder setType(int); - method @NonNull public android.telephony.data.DataProfile.Builder setUserName(@NonNull String); + method @Deprecated @NonNull public android.telephony.data.DataProfile.Builder setUserName(@NonNull String); } public abstract class DataService extends android.app.Service { diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index d0096fdec82e..f7d5e52e3675 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -380,6 +380,8 @@ public class ActivityOptions extends ComponentOptions { public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12; /** @hide */ public static final int ANIM_REMOTE_ANIMATION = 13; + /** @hide */ + public static final int ANIM_FROM_STYLE = 14; private String mPackageName; private Rect mLaunchBounds; diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index 0d85fb9488be..d7b2ab4351a4 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -22,8 +22,11 @@ import android.annotation.SystemApi; import android.compat.Compatibility; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.CompatibilityOverridesByPackageConfig; +import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig; import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; import java.util.Map; @@ -98,6 +101,31 @@ public final class CompatChanges { } /** + * Equivalent to calling {@link #putPackageOverrides(String, Map)} on each entry in {@code + * packageNameToOverrides}, but the state of the compat config will be updated only once + * instead of for each package. + * + * @param packageNameToOverrides A map from package name to a map from change ID to the + * override applied for that package name and change ID. + */ + @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) + public static void putAllPackageOverrides( + @NonNull Map<String, Map<Long, PackageOverride>> packageNameToOverrides) { + ArrayMap<String, CompatibilityOverrideConfig> packageNameToConfig = new ArrayMap<>(); + for (String packageName : packageNameToOverrides.keySet()) { + packageNameToConfig.put(packageName, + new CompatibilityOverrideConfig(packageNameToOverrides.get(packageName))); + } + CompatibilityOverridesByPackageConfig config = new CompatibilityOverridesByPackageConfig( + packageNameToConfig); + try { + QUERY_CACHE.getPlatformCompatService().putAllOverridesOnReleaseBuilds(config); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Associates app compat overrides with the given package and their respective change IDs. * This will check whether the caller is allowed to perform this operation on the given apk and * build. Only the installer package is allowed to set overrides on a non-debuggable final @@ -123,6 +151,33 @@ public final class CompatChanges { } /** + * Equivalent to calling {@link #removePackageOverrides(String, Set)} on each entry in {@code + * packageNameToOverridesToRemove}, but the state of the compat config will be updated only once + * instead of for each package. + * + * @param packageNameToOverridesToRemove A map from package name to a set of change IDs for + * which to remove overrides for that package name. + */ + @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) + public static void removeAllPackageOverrides( + @NonNull Map<String, Set<Long>> packageNameToOverridesToRemove) { + ArrayMap<String, CompatibilityOverridesToRemoveConfig> packageNameToConfig = + new ArrayMap<>(); + for (String packageName : packageNameToOverridesToRemove.keySet()) { + packageNameToConfig.put(packageName, + new CompatibilityOverridesToRemoveConfig( + packageNameToOverridesToRemove.get(packageName))); + } + CompatibilityOverridesToRemoveByPackageConfig config = + new CompatibilityOverridesToRemoveByPackageConfig(packageNameToConfig); + try { + QUERY_CACHE.getPlatformCompatService().removeAllOverridesOnReleaseBuilds(config); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Removes app compat overrides for the given package. This will check whether the caller is * allowed to perform this operation on the given apk and build. Only the installer package is * allowed to clear overrides on a non-debuggable final build and a non-test apk. diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java index fd1b9e3bede2..db3a1921c1ba 100644 --- a/core/java/android/app/prediction/AppPredictor.java +++ b/core/java/android/app/prediction/AppPredictor.java @@ -105,7 +105,7 @@ public final class AppPredictor { e.rethrowAsRuntimeException(); } - mCloseGuard.open("close"); + mCloseGuard.open("AppPredictor.close"); } /** diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java index a5425a20655a..2cd1d96190b0 100644 --- a/core/java/android/app/search/SearchSession.java +++ b/core/java/android/app/search/SearchSession.java @@ -106,7 +106,7 @@ public final class SearchSession implements AutoCloseable{ e.rethrowFromSystemServer(); } - mCloseGuard.open("close"); + mCloseGuard.open("SearchSession.close"); } /** diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java index 9199581c3149..b523be2cc7e9 100644 --- a/core/java/android/app/smartspace/SmartspaceSession.java +++ b/core/java/android/app/smartspace/SmartspaceSession.java @@ -107,7 +107,7 @@ public final class SmartspaceSession implements AutoCloseable { e.rethrowFromSystemServer(); } - mCloseGuard.open("close"); + mCloseGuard.open("SmartspaceSession.close"); } /** diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 518e7534d512..cc3c01241c66 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -109,7 +109,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { mAuthority = authority; mStable = stable; - mCloseGuard.open("close"); + mCloseGuard.open("ContentProviderClient.close"); } /** @@ -695,7 +695,7 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable { CursorWrapperInner(Cursor cursor) { super(cursor); - mCloseGuard.open("close"); + mCloseGuard.open("CursorWrapperInner.close"); } @Override diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 184acb1a81ef..01d231c51751 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -3858,7 +3858,7 @@ public abstract class ContentResolver implements ContentInterface { CursorWrapperInner(Cursor cursor, IContentProvider contentProvider) { super(cursor); mContentProvider = contentProvider; - mCloseGuard.open("close"); + mCloseGuard.open("CursorWrapperInner.close"); } @Override diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 1e6029f951fc..2ff29cbdcc2a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5598,7 +5598,9 @@ public class Intent implements Parcelable, Cloneable { * * <p>Targets provided in this way will be presented inline with all other targets provided * by services from other apps. They will be prioritized before other service targets, but - * after those targets provided by sources that the user has manually pinned to the front.</p> + * after those targets provided by sources that the user has manually pinned to the front. + * You can provide up to two targets on this extra (the limit of two targets + * starts in Android 10).</p> * * @see #ACTION_CHOOSER */ @@ -5709,9 +5711,11 @@ public class Intent implements Parcelable, Cloneable { /** * A Parcelable[] of {@link Intent} or * {@link android.content.pm.LabeledIntent} objects as set with - * {@link #putExtra(String, Parcelable[])} of additional activities to place - * a the front of the list of choices, when shown to the user with a - * {@link #ACTION_CHOOSER}. + * {@link #putExtra(String, Parcelable[])} to place + * at the front of the list of choices, when shown to the user with an + * {@link #ACTION_CHOOSER}. You can choose up to two additional activities + * to show before the app suggestions (the limit of two additional activities starts in + * Android 10). */ public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index cf25c3c56208..69d573f84975 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -224,7 +224,7 @@ public abstract class AbstractCursor implements CrossProcessCursor { /* Implementation */ public AbstractCursor() { mPos = -1; - mCloseGuard.open("close"); + mCloseGuard.open("AbstractCursor.close"); } @Override diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index ccb7cf19d0b1..f13c79587a28 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -142,7 +142,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { if (mWindowPtr == 0) { throw new AssertionError(); // Not possible, the native code won't return it. } - mCloseGuard.open("close"); + mCloseGuard.open("CursorWindow.close"); } /** @@ -170,7 +170,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { throw new AssertionError(); // Not possible, the native code won't return it. } mName = nativeGetName(mWindowPtr); - mCloseGuard.open("close"); + mCloseGuard.open("CursorWindow.close"); } @Override diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 328858b260ac..6d6ec06182d6 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -179,7 +179,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase(); mPreparedStatementCache = new PreparedStatementCache( mConfiguration.maxSqlCacheSize); - mCloseGuard.open("close"); + mCloseGuard.open("SQLiteConnection.close"); } @Override diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index d3ad6bb27b3c..216c9c26424d 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -218,7 +218,7 @@ public final class SQLiteConnectionPool implements Closeable { // Mark the pool as being open for business. mIsOpen = true; - mCloseGuard.open("close"); + mCloseGuard.open("SQLiteConnectionPool.close"); } /** diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index a4a8f313e3ba..4683d252b68a 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -253,7 +253,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { NativeAllocationRegistry registry = new NativeAllocationRegistry( loader, nGetNativeFinalizer(), bufferSize); mCleaner = registry.registerNativeAllocation(this, mNativeObject); - mCloseGuard.open("close"); + mCloseGuard.open("HardwareBuffer.close"); } @Override diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index e9fffa30ae57..282f1d343959 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -687,7 +687,7 @@ public class SystemSensorManager extends SensorManager { new WeakReference<>(this), looper.getQueue(), packageName, mode, manager.mContext.getOpPackageName(), manager.mContext.getAttributionTag()); - mCloseGuard.open("dispose"); + mCloseGuard.open("BaseEventQueue.dispose"); mManager = manager; } diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 19fb845d9384..2ac194b67192 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -144,4 +144,6 @@ interface IInputManager { void openLightSession(int deviceId, String opPkg, in IBinder token); void closeLightSession(int deviceId, in IBinder token); + + void cancelCurrentTouch(); } diff --git a/core/java/android/hardware/input/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java index 885df7be2510..802e6dde497a 100644 --- a/core/java/android/hardware/input/InputDeviceLightsManager.java +++ b/core/java/android/hardware/input/InputDeviceLightsManager.java @@ -100,7 +100,7 @@ class InputDeviceLightsManager extends LightsManager { * Instantiated by {@link LightsManager#openSession()}. */ private InputDeviceLightsSession() { - mCloseGuard.open("close"); + mCloseGuard.open("InputDeviceLightsSession.close"); } /** diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 6f0c944b76ff..6253fb90323a 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1650,6 +1650,18 @@ public final class InputManager { } /** + * Cancel all ongoing pointer gestures on all displays. + * @hide + */ + public void cancelCurrentTouch() { + try { + mIm.cancelCurrentTouch(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Listens for changes in input devices. */ public interface InputDeviceListener { diff --git a/core/java/android/hardware/lights/SystemLightsManager.java b/core/java/android/hardware/lights/SystemLightsManager.java index d0df611e7842..055a7f43f9ed 100644 --- a/core/java/android/hardware/lights/SystemLightsManager.java +++ b/core/java/android/hardware/lights/SystemLightsManager.java @@ -145,7 +145,7 @@ public final class SystemLightsManager extends LightsManager { */ @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) private SystemLightsSession() { - mCloseGuard.open("close"); + mCloseGuard.open("SystemLightsSession.close"); } /** diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index a525f58371f5..9468ca2590bb 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * A class describing a client of the Context Hub Service. - * + * <p> * Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported * by this object are thread-safe and can be used without external synchronization. * @@ -69,7 +69,7 @@ public class ContextHubClient implements Closeable { mCloseGuard = null; } else { mCloseGuard = CloseGuard.get(); - mCloseGuard.open("close"); + mCloseGuard.open("ContextHubClient.close"); } } @@ -110,7 +110,7 @@ public class ContextHubClient implements Closeable { * This value can be used as an identifier for the messaging channel between a * ContextHubClient and the Context Hub. This may be used as a routing mechanism * between various ContextHubClient objects within an application. - * + * <p> * The value returned by this method will remain the same if it is associated with * the same client reference at the ContextHubService (for instance, the ID of a * PendingIntent ContextHubClient will remain the same even if the local object @@ -119,8 +119,6 @@ public class ContextHubClient implements Closeable { * of a non-equal PendingIntent client), the ID will not be the same. * * @return The ID of this ContextHubClient. - * - * @throws IllegalStateException if the ID was not set internally. */ public int getId() { if (mId == null) { @@ -135,7 +133,7 @@ public class ContextHubClient implements Closeable { * When this function is invoked, the messaging associated with this client is invalidated. * All futures messages targeted for this client are dropped at the service, and the * ContextHubClient is unregistered from the service. - * + * <p> * If this object has a PendingIntent, i.e. the object was generated via * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, long)}, then the * Intent events corresponding to the PendingIntent will no longer be triggered. @@ -158,7 +156,7 @@ public class ContextHubClient implements Closeable { * * This function returns RESULT_SUCCESS if the message has reached the HAL, but * does not guarantee delivery of the message to the target nanoapp. - * + * <p> * Before sending the first message to your nanoapp, it's recommended that the following * operations should be performed: * 1) Invoke {@link ContextHubManager#queryNanoApps(ContextHubInfo)} to verify the nanoapp is diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 1c35cb66ada8..60d8cacd19be 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -69,7 +69,7 @@ public class UsbDeviceConnection { boolean wasOpened = native_open(name, pfd.getFileDescriptor()); if (wasOpened) { - mCloseGuard.open("close"); + mCloseGuard.open("UsbDeviceConnection.close"); } return wasOpened; diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index d1c6465d62c8..6ac5e8de8fa7 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -103,7 +103,7 @@ public class UsbRequest { endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval()); if (wasInitialized) { - mCloseGuard.open("close"); + mCloseGuard.open("UsbRequest.close"); } return wasInitialized; diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index f33a035673b8..a6ab1381aa57 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -18,8 +18,11 @@ package android.net; import static android.net.NetworkStats.METERED_ALL; import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkTemplate.MATCH_BLUETOOTH; import static android.net.NetworkTemplate.MATCH_CARRIER; +import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE; +import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT; import android.annotation.NonNull; @@ -324,7 +327,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { @NonNull private byte[] getNetworkTemplateBytesForBackup() throws IOException { - if (!template.isPersistable()) { + if (!isTemplatePersistable(this.template)) { Log.wtf(TAG, "Trying to backup non-persistable template: " + this); } @@ -378,4 +381,28 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { "Restored network template contains unknown match rule " + matchRule, e); } } + + /** + * Check if the template can be persisted into disk. + */ + public static boolean isTemplatePersistable(@NonNull NetworkTemplate template) { + switch (template.getMatchRule()) { + case MATCH_BLUETOOTH: + case MATCH_ETHERNET: + return true; + case MATCH_CARRIER: + case MATCH_MOBILE: + return !template.getSubscriberIds().isEmpty(); + case MATCH_WIFI: + if (Objects.equals(template.getWifiNetworkKey(), null) + && template.getSubscriberIds().isEmpty()) { + return false; + } + return true; + default: + // Don't allow persistable for unknown types or legacy types such as + // MATCH_MOBILE_WILDCARD, MATCH_PROXY, etc. + return false; + } + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index edacffca494f..f5777ed0c8b3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5945,7 +5945,6 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.CONNECTIVITY_CHANGE_DELAY); MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED); MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_SERVER); - MOVED_TO_GLOBAL.add(Settings.Global.NSD_ON); MOVED_TO_GLOBAL.add(Settings.Global.SET_INSTALL_LOCATION); MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_INSTALL_LOCATION); MOVED_TO_GLOBAL.add(Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY); @@ -12857,13 +12856,6 @@ public final class Settings { @Readable public static final String MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS = "min_duration_between_recovery_steps"; - /** - * Whether network service discovery is enabled. - * - * @hide - */ - @Readable - public static final String NSD_ON = "nsd_on"; /** * Let user pick default install location. diff --git a/core/java/android/service/games/CreateGameSessionRequest.aidl b/core/java/android/service/games/CreateGameSessionRequest.aidl new file mode 100644 index 000000000000..e09cc8e55e3c --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionRequest.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 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.service.games; + + +/** + * @hide + */ +parcelable CreateGameSessionRequest;
\ No newline at end of file diff --git a/core/java/android/service/games/CreateGameSessionRequest.java b/core/java/android/service/games/CreateGameSessionRequest.java new file mode 100644 index 000000000000..2129cb165b93 --- /dev/null +++ b/core/java/android/service/games/CreateGameSessionRequest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 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.service.games; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Request object providing the context in order to create a new {@link GameSession}. + * + * This is provided to the Game Service provider via + * {@link GameSessionService#onNewSession(CreateGameSessionRequest)}. It includes game + * (see {@link #getGamePackageName()}) that the session is associated with and a task + * (see {@link #getTaskId()}. + * + * @hide + */ +@SystemApi +public final class CreateGameSessionRequest implements Parcelable { + + @NonNull + public static final Parcelable.Creator<CreateGameSessionRequest> CREATOR = + new Parcelable.Creator<CreateGameSessionRequest>() { + @Override + public CreateGameSessionRequest createFromParcel(Parcel source) { + return new CreateGameSessionRequest( + source.readInt(), + source.readString8()); + } + + @Override + public CreateGameSessionRequest[] newArray(int size) { + return new CreateGameSessionRequest[0]; + } + }; + + private final int mTaskId; + private final String mGamePackageName; + + public CreateGameSessionRequest(int taskId, @NonNull String gamePackageName) { + this.mTaskId = taskId; + this.mGamePackageName = gamePackageName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mTaskId); + dest.writeString8(mGamePackageName); + } + + /** + * Unique identifier for the task. + */ + public int getTaskId() { + return mTaskId; + } + + /** + * The package name of the game associated with the session. + */ + @NonNull + public String getGamePackageName() { + return mGamePackageName; + } + + @Override + public String toString() { + return "GameSessionRequest{" + + "mTaskId=" + + mTaskId + + ", mGamePackageName='" + + mGamePackageName + + "\'}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof CreateGameSessionRequest)) { + return false; + } + + CreateGameSessionRequest that = (CreateGameSessionRequest) o; + return mTaskId == that.mTaskId + && Objects.equals(mGamePackageName, that.mGamePackageName); + } + + @Override + public int hashCode() { + return Objects.hash(mTaskId, mGamePackageName); + } +} diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java index 4b440ddf5405..b79c0553460f 100644 --- a/core/java/android/service/games/GameService.java +++ b/core/java/android/service/games/GameService.java @@ -46,7 +46,7 @@ import java.util.Objects; */ @SystemApi public class GameService extends Service { - static final String TAG = "GameService"; + private static final String TAG = "GameService"; /** * The {@link Intent} that must be declared as handled by the service. @@ -55,8 +55,16 @@ public class GameService extends Service { * that other applications can not abuse it. */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) - public static final String SERVICE_INTERFACE = - "android.service.games.GameService"; + public static final String ACTION_GAME_SERVICE = + "android.service.games.action.GAME_SERVICE"; + + /** + * Name under which a GameService component publishes information about itself. + * This meta-data should reference an XML resource containing a + * <code><{@link + * android.R.styleable#GameService game-session-service}></code> tag. + */ + public static final String SERVICE_META_DATA = "android.game_service"; private IGameManagerService mGameManagerService; private final IGameService mInterface = new IGameService.Stub() { @@ -72,6 +80,7 @@ public class GameService extends Service { GameService::onDisconnected, GameService.this)); } }; + private final IBinder.DeathRecipient mGameManagerServiceDeathRecipient = () -> { Log.w(TAG, "System service binder died. Shutting down"); @@ -82,9 +91,10 @@ public class GameService extends Service { @Override @Nullable public IBinder onBind(@Nullable Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { + if (ACTION_GAME_SERVICE.equals(intent.getAction())) { return mInterface.asBinder(); } + return null; } diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java new file mode 100644 index 000000000000..0ff08c08932b --- /dev/null +++ b/core/java/android/service/games/GameSession.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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.service.games; + +import android.annotation.SystemApi; +import android.os.Handler; + +import com.android.internal.util.function.pooled.PooledLambda; + +/** + * An active game session, providing a facility for the implementation to interact with the game. + * + * A Game Service provider should extend the {@link GameSession} to provide their own implementation + * which is then returned when a game session is created via + * {@link GameSessionService#onNewSession(CreateGameSessionRequest)}. + * + * @hide + */ +@SystemApi +public abstract class GameSession { + + final IGameSession mInterface = new IGameSession.Stub() { + @Override + public void destroy() { + Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( + GameSession::doDestroy, GameSession.this)); + } + }; + + void doCreate() { + onCreate(); + } + + void doDestroy() { + onDestroy(); + } + + /** + * Initializer called when the game session is starting. + * + * This should be used perform any setup required now that the game session is created. + */ + public void onCreate() {} + + /** + * Finalizer called when the game session is ending. + * + * This should be used to perform any cleanup before the game session is destroyed. + */ + public void onDestroy() {} +} diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java new file mode 100644 index 000000000000..a2c88709b62d --- /dev/null +++ b/core/java/android/service/games/GameSessionService.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 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.service.games; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; + +import com.android.internal.infra.AndroidFuture; +import com.android.internal.util.function.pooled.PooledLambda; + +import java.util.Objects; + +/** + * Service that hosts active game sessions. + * + * This service should be in a separate process from the {@link GameService}. This + * allows it to perform the heavyweight operations associated with rendering a game + * session overlay while games are running and release these resources (by allowing + * the process to be killed) when games are not running. + * + * Game Service providers must extend {@link GameSessionService} and declare the service in their + * Manifest. The service must require the {@link android.Manifest.permission#BIND_GAME_SERVICE} so + * that other application can not abuse it. This service is used to create instances of + * {@link GameSession} via {@link #onNewSession(CreateGameSessionRequest)} and will remain bound to + * so long as at least one {@link GameSession} is running. + * + * @hide + */ +@SystemApi +public abstract class GameSessionService extends Service { + private static final String TAG = "GameSessionService"; + + /** + * The {@link Intent} action used when binding to the service. + * To be supported, the service must require the + * {@link android.Manifest.permission#BIND_GAME_SERVICE} permission so + * that other applications can not abuse it. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String ACTION_GAME_SESSION_SERVICE = + "android.service.games.action.GAME_SESSION_SERVICE"; + + private final IGameSessionService mInterface = new IGameSessionService.Stub() { + @Override + public void create(CreateGameSessionRequest createGameSessionRequest, + AndroidFuture gameSessionFuture) { + Handler.getMain().post(PooledLambda.obtainRunnable( + GameSessionService::doCreate, GameSessionService.this, + createGameSessionRequest, + gameSessionFuture)); + } + }; + + @Override + @Nullable + public IBinder onBind(@Nullable Intent intent) { + if (intent == null) { + return null; + } + + if (!ACTION_GAME_SESSION_SERVICE.equals(intent.getAction())) { + return null; + } + + return mInterface.asBinder(); + } + + private void doCreate(CreateGameSessionRequest createGameSessionRequest, + AndroidFuture<IBinder> gameSessionFuture) { + GameSession gameSession = onNewSession(createGameSessionRequest); + Objects.requireNonNull(gameSession); + + gameSessionFuture.complete(gameSession.mInterface.asBinder()); + + gameSession.doCreate(); + } + + /** + * Request to create a new {@link GameSession}. + */ + @NonNull + public abstract GameSession onNewSession( + @NonNull CreateGameSessionRequest createGameSessionRequest); +} diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl new file mode 100644 index 000000000000..b2e9f1d21f6e --- /dev/null +++ b/core/java/android/service/games/IGameSession.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 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.service.games; + +/** + * @hide + */ +oneway interface IGameSession { + void destroy(); +} diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl new file mode 100644 index 000000000000..2a53ea7f8e4a --- /dev/null +++ b/core/java/android/service/games/IGameSessionService.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 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.service.games; + +import android.service.games.IGameSession; +import android.service.games.CreateGameSessionRequest; + +import com.android.internal.infra.AndroidFuture; + + +/** + * @hide + */ +oneway interface IGameSessionService { + void create( + in CreateGameSessionRequest createGameSessionRequest, + in AndroidFuture /* T=IBinder for IGameSession */ gameSessionFuture); +} diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 5b9d69c2f9ff..e5c9adba46a9 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -743,6 +743,7 @@ public class PhoneStateListener { * @see TelephonyManager#DATA_CONNECTING * @see TelephonyManager#DATA_CONNECTED * @see TelephonyManager#DATA_SUSPENDED + * @see TelephonyManager#DATA_HANDOVER_IN_PROGRESS * @deprecated Use {@link TelephonyCallback.DataConnectionStateListener} instead. */ @Deprecated diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index 3028a6d8f97a..baa9e6b184e9 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -792,6 +792,7 @@ public class TelephonyCallback { * @see TelephonyManager#DATA_CONNECTING * @see TelephonyManager#DATA_CONNECTED * @see TelephonyManager#DATA_SUSPENDED + * @see TelephonyManager#DATA_HANDOVER_IN_PROGRESS */ void onDataConnectionStateChanged(@TelephonyManager.DataState int state, @Annotation.NetworkType int networkType); diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java index 6da38c2c2acb..5cbbbef2cf88 100644 --- a/core/java/android/util/MemoryIntArray.java +++ b/core/java/android/util/MemoryIntArray.java @@ -75,7 +75,7 @@ public final class MemoryIntArray implements Parcelable, Closeable { final String name = UUID.randomUUID().toString(); mFd = nativeCreate(name, size); mMemoryAddr = nativeOpen(mFd, mIsOwner); - mCloseGuard.open("close"); + mCloseGuard.open("MemoryIntArray.close"); } private MemoryIntArray(Parcel parcel) throws IOException { @@ -86,7 +86,7 @@ public final class MemoryIntArray implements Parcelable, Closeable { } mFd = pfd.detachFd(); mMemoryAddr = nativeOpen(mFd, mIsOwner); - mCloseGuard.open("close"); + mCloseGuard.open("MemoryIntArray.close"); } /** diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java index c9abec989cd1..a24c1f95b0c0 100644 --- a/core/java/android/view/InputEventReceiver.java +++ b/core/java/android/view/InputEventReceiver.java @@ -79,7 +79,7 @@ public abstract class InputEventReceiver { mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this), inputChannel, mMessageQueue); - mCloseGuard.open("dispose"); + mCloseGuard.open("InputEventReceiver.dispose"); } @Override diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java index d14421897860..9035f3f7a0d4 100644 --- a/core/java/android/view/InputEventSender.java +++ b/core/java/android/view/InputEventSender.java @@ -67,7 +67,7 @@ public abstract class InputEventSender { mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this), inputChannel, mMessageQueue); - mCloseGuard.open("dispose"); + mCloseGuard.open("InputEventSender.dispose"); } @Override diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java index 7accb66aa3aa..ff51ebcca08e 100644 --- a/core/java/android/view/InputQueue.java +++ b/core/java/android/view/InputQueue.java @@ -52,7 +52,7 @@ public final class InputQueue { public InputQueue() { mPtr = nativeInit(new WeakReference<InputQueue>(this), Looper.myQueue()); - mCloseGuard.open("dispose"); + mCloseGuard.open("InputQueue.dispose"); } @Override diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index bf9de39124c9..b1582cf9f023 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -180,10 +180,7 @@ public class InsetsSourceConsumer { // If we have a new leash, make sure visibility is up-to-date, even though we // didn't want to run an animation above. - SurfaceControl newLeash = mSourceControl.getLeash(); - if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { - applyHiddenToControl(); - } + applyRequestedVisibilityToControl(); // Remove the surface that owned by last control when it lost. if (!requestedVisible && !mIsAnimationPending && lastControl == null) { @@ -388,18 +385,20 @@ public class InsetsSourceConsumer { } } - private void applyHiddenToControl() { + private void applyRequestedVisibilityToControl() { if (mSourceControl == null || mSourceControl.getLeash() == null) { return; } final Transaction t = mTransactionSupplier.get(); - if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible); + if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible); if (mRequestedVisible) { t.show(mSourceControl.getLeash()); } else { t.hide(mSourceControl.getLeash()); } + // Ensure the alpha value is aligned with the actual requested visibility. + t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0); t.apply(); onPerceptible(mRequestedVisible); } diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java index 278b2fcc3678..cba0e970d389 100644 --- a/core/java/android/view/ScrollCaptureConnection.java +++ b/core/java/android/view/ScrollCaptureConnection.java @@ -86,7 +86,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple @Override public ICancellationSignal startCapture(@NonNull Surface surface, @NonNull IScrollCaptureCallbacks remote) throws RemoteException { - mCloseGuard.open("close"); + mCloseGuard.open("ScrollCaptureConnection.close"); if (!surface.isValid()) { throw new RemoteException(new IllegalArgumentException("surface must be valid")); diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 904aa73f6ac4..e5ec260907df 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -755,7 +755,7 @@ public class Surface implements Parcelable { private void setNativeObjectLocked(long ptr) { if (mNativeObject != ptr) { if (mNativeObject == 0 && ptr != 0) { - mCloseGuard.open("release"); + mCloseGuard.open("Surface.release"); } else if (mNativeObject != 0 && ptr == 0) { mCloseGuard.close(); } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 7208930c0b20..915c8fb9a6dd 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -18,6 +18,7 @@ package android.window; import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.app.ActivityOptions.ANIM_FROM_STYLE; import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; import static android.app.ActivityOptions.ANIM_SCALE_UP; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; @@ -46,6 +47,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import java.util.ArrayList; import java.util.List; @@ -581,6 +583,7 @@ public final class TransitionInfo implements Parcelable { private String mPackageName; private final Rect mTransitionBounds = new Rect(); private HardwareBuffer mThumbnail; + private int mAnimations; private AnimationOptions(int type) { mType = type; @@ -594,6 +597,15 @@ public final class TransitionInfo implements Parcelable { mPackageName = in.readString(); mTransitionBounds.readFromParcel(in); mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR); + mAnimations = in.readInt(); + } + + public static AnimationOptions makeAnimOptionsFromLayoutParameters( + WindowManager.LayoutParams lp) { + AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE); + options.mPackageName = lp.packageName; + options.mAnimations = lp.windowAnimations; + return options; } public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId, @@ -662,6 +674,10 @@ public final class TransitionInfo implements Parcelable { return mThumbnail; } + public int getAnimations() { + return mAnimations; + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); @@ -671,6 +687,7 @@ public final class TransitionInfo implements Parcelable { dest.writeString(mPackageName); mTransitionBounds.writeToParcel(dest, flags); dest.writeTypedObject(mThumbnail, flags); + dest.writeInt(mAnimations); } @NonNull diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.aidl new file mode 100644 index 000000000000..eed401559e37 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.internal.compat; + +parcelable CompatibilityOverridesByPackageConfig; diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java new file mode 100644 index 000000000000..8652bb6d05e4 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 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.internal.compat; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Parcelable containing compat config overrides by application. + * @hide + */ +public final class CompatibilityOverridesByPackageConfig implements Parcelable { + public final Map<String, CompatibilityOverrideConfig> packageNameToOverrides; + + public CompatibilityOverridesByPackageConfig( + Map<String, CompatibilityOverrideConfig> packageNameToOverrides) { + this.packageNameToOverrides = packageNameToOverrides; + } + + private CompatibilityOverridesByPackageConfig(Parcel in) { + int keyCount = in.readInt(); + packageNameToOverrides = new HashMap<>(); + for (int i = 0; i < keyCount; i++) { + String key = in.readString(); + packageNameToOverrides.put(key, + CompatibilityOverrideConfig.CREATOR.createFromParcel(in)); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(packageNameToOverrides.size()); + for (String key : packageNameToOverrides.keySet()) { + dest.writeString(key); + packageNameToOverrides.get(key).writeToParcel(dest, /* flags= */ 0); + } + } + + public static final Parcelable.Creator<CompatibilityOverridesByPackageConfig> CREATOR = + new Parcelable.Creator<CompatibilityOverridesByPackageConfig>() { + + @Override + public CompatibilityOverridesByPackageConfig createFromParcel(Parcel in) { + return new CompatibilityOverridesByPackageConfig(in); + } + + @Override + public CompatibilityOverridesByPackageConfig[] newArray(int size) { + return new CompatibilityOverridesByPackageConfig[size]; + } + }; +} diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.aidl new file mode 100644 index 000000000000..b9d0cef325dd --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.internal.compat; + +parcelable CompatibilityOverridesToRemoveByPackageConfig; diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java new file mode 100644 index 000000000000..b408d6440070 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 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.internal.compat; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Parcelable containing compat config change IDs for which to remove overrides by application. + * + * <p>This class is separate from CompatibilityOverridesByPackageConfig since we only need change + * IDs. + * @hide + */ +public final class CompatibilityOverridesToRemoveByPackageConfig implements Parcelable { + public final Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove; + + public CompatibilityOverridesToRemoveByPackageConfig( + Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove) { + this.packageNameToOverridesToRemove = packageNameToOverridesToRemove; + } + + private CompatibilityOverridesToRemoveByPackageConfig(Parcel in) { + int keyCount = in.readInt(); + packageNameToOverridesToRemove = new HashMap<>(); + for (int i = 0; i < keyCount; i++) { + String key = in.readString(); + packageNameToOverridesToRemove.put(key, + CompatibilityOverridesToRemoveConfig.CREATOR.createFromParcel(in)); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(packageNameToOverridesToRemove.size()); + for (String key : packageNameToOverridesToRemove.keySet()) { + dest.writeString(key); + packageNameToOverridesToRemove.get(key).writeToParcel(dest, /* flags= */ 0); + } + } + + public static final Parcelable.Creator<CompatibilityOverridesToRemoveByPackageConfig> CREATOR = + new Parcelable.Creator<CompatibilityOverridesToRemoveByPackageConfig>() { + + @Override + public CompatibilityOverridesToRemoveByPackageConfig createFromParcel(Parcel in) { + return new CompatibilityOverridesToRemoveByPackageConfig(in); + } + + @Override + public CompatibilityOverridesToRemoveByPackageConfig[] newArray(int size) { + return new CompatibilityOverridesToRemoveByPackageConfig[size]; + } + }; +} diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java index 642f79ca7afa..e85afefdc39a 100644 --- a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java +++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java @@ -26,6 +26,8 @@ import java.util.Set; /** * Parcelable containing compat config change IDs for which to remove overrides for a given * application. + * + * <p>This class is separate from CompatibilityOverrideConfig since we only need change IDs. * @hide */ public final class CompatibilityOverridesToRemoveConfig implements Parcelable { diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl index 418d16e07a75..8847a490e39c 100644 --- a/core/java/com/android/internal/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -22,6 +22,8 @@ import java.util.Map; parcelable CompatibilityChangeConfig; parcelable CompatibilityOverrideConfig; +parcelable CompatibilityOverridesByPackageConfig; +parcelable CompatibilityOverridesToRemoveByPackageConfig; parcelable CompatibilityOverridesToRemoveConfig; parcelable CompatibilityChangeInfo; /** @@ -152,6 +154,26 @@ interface IPlatformCompat { void setOverrides(in CompatibilityChangeConfig overrides, in String packageName); /** + * Adds overrides to compatibility changes on release builds for multiple apps. + * + * <p>The caller to this API needs to hold + * {@code android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} and all change ids + * in {@code overridesByPackage} need to annotated with {@link + * android.compat.annotation.Overridable}. + * + * A release build in this definition means that {@link android.os.Build#IS_DEBUGGABLE} needs to + * be {@code false}. + * + * <p>Note that this does not kill the app, and therefore overrides read from the app process + * will not be updated. Overrides read from the system process do take effect. + * + * @param overridesByPackage parcelable containing the compat change overrides to be applied + * on specific apps by their package name + * @throws SecurityException if overriding changes is not permitted + */ + void putAllOverridesOnReleaseBuilds(in CompatibilityOverridesByPackageConfig overridesByPackage); + + /** * Adds overrides to compatibility changes on release builds. * * <p>The caller to this API needs to hold @@ -206,6 +228,26 @@ interface IPlatformCompat { boolean clearOverrideForTest(long changeId, String packageName); /** + * Restores the default behaviour for compatibility changes on release builds for multiple apps. + * + * <p>The caller to this API needs to hold + * {@code android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} and all change ids + * in {@code overridesToRemoveByPackage} need to annotated with {@link + * android.compat.annotation.Overridable}. + * + * A release build in this definition means that {@link android.os.Build#IS_DEBUGGABLE} needs to + * be {@code false}. + * + * <p>Note that this does not kill the app, and therefore overrides read from the app process + * will not be updated. Overrides read from the system process do take effect. + * + * @param overridesToRemoveByPackage parcelable containing the compat change overrides to be + * removed for specific apps by their package name + * @throws SecurityException if overriding changes is not permitted + */ + void removeAllOverridesOnReleaseBuilds(in CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage); + + /** * Restores the default behaviour for compatibility changes on release builds. * * <p>The caller to this API needs to hold diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 6a6f60e967c1..f90461048a3b 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -157,6 +157,12 @@ public final class SystemUiDeviceConfigFlags { */ public static final String PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled"; + /** + * Whether to show old location indicator on all location accesses. + */ + public static final String PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED = + "location_indicators_small_enabled"; + // Flags related to Assistant /** diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index ea38db304e6d..0d4ad382222f 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -42,6 +42,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; @@ -177,6 +179,8 @@ public class InteractionJankMonitor { public static final int CUJ_USER_SWITCH = 37; public static final int CUJ_SPLASHSCREEN_AVD = 38; public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; + public static final int CUJ_SCREEN_OFF = 40; + public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41; private static final int NO_STATSD_LOGGING = -1; @@ -225,6 +229,8 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD, }; private static volatile InteractionJankMonitor sInstance; @@ -285,6 +291,8 @@ public class InteractionJankMonitor { CUJ_USER_SWITCH, CUJ_SPLASHSCREEN_AVD, CUJ_SPLASHSCREEN_EXIT_ANIM, + CUJ_SCREEN_OFF, + CUJ_SCREEN_OFF_SHOW_AOD, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -423,6 +431,16 @@ public class InteractionJankMonitor { } /** + * @param cujType cuj type + * @return true if the cuj is under instrumenting, false otherwise. + */ + public boolean isInstrumenting(@CujType int cujType) { + synchronized (mLock) { + return mRunningTrackers.contains(cujType); + } + } + + /** * Begins a trace session. * * @param v an attached view. @@ -690,6 +708,10 @@ public class InteractionJankMonitor { return "SPLASHSCREEN_AVD"; case CUJ_SPLASHSCREEN_EXIT_ANIM: return "SPLASHSCREEN_EXIT_ANIM"; + case CUJ_SCREEN_OFF: + return "SCREEN_OFF"; + case CUJ_SCREEN_OFF_SHOW_AOD: + return "SCREEN_OFF_SHOW_AOD"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index d3224b13e312..faea7706ee14 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -260,25 +260,39 @@ public class TransitionAnimation { return null; } - /** Load animation by attribute Id from android package. */ + /** Load animation by attribute Id from a specific AnimationStyle resource. */ @Nullable - public Animation loadDefaultAnimationAttr(int animAttr) { + public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr, + boolean translucent) { + if (animStyleResId == 0) { + return null; + } int resId = Resources.ID_NULL; Context context = mContext; if (animAttr >= 0) { - AttributeCache.Entry ent = getCachedAnimations(DEFAULT_PACKAGE, - mDefaultWindowAnimationStyleResId); + packageName = packageName != null ? packageName : DEFAULT_PACKAGE; + AttributeCache.Entry ent = getCachedAnimations(packageName, animStyleResId); if (ent != null) { context = ent.context; resId = ent.array.getResourceId(animAttr, 0); } } + if (translucent) { + resId = updateToTranslucentAnimIfNeeded(resId); + } if (ResourceId.isValid(resId)) { return loadAnimationSafely(context, resId, mTag); } return null; } + /** Load animation by attribute Id from android package. */ + @Nullable + public Animation loadDefaultAnimationAttr(int animAttr) { + return loadAnimationAttr(DEFAULT_PACKAGE, mDefaultWindowAnimationStyleResId, animAttr, + false /* translucent */); + } + @Nullable private AttributeCache.Entry getCachedAnimations(LayoutParams lp) { if (mDebug) { @@ -1024,6 +1038,16 @@ public class TransitionAnimation { return anim; } + private static int updateToTranslucentAnimIfNeeded(int anim) { + if (anim == R.anim.activity_open_enter) { + return R.anim.activity_translucent_open_enter; + } + if (anim == R.anim.activity_close_exit) { + return R.anim.activity_translucent_close_exit; + } + return anim; + } + private static @TransitionOldType int getTransitCompatType(@TransitionType int transit, int wallpaperTransit) { if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 21402b6602eb..0eb8c6a3ecf3 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -83,6 +83,7 @@ static void init() { constexpr int INDIC_MIN_PREFIX = 2; constexpr int INDIC_MIN_SUFFIX = 2; + addHyphenator("am", 1, 1); // Amharic addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese addHyphenator("be", 2, 2); // Belarusian addHyphenator("bg", 2, 2); // Bulgarian @@ -100,6 +101,7 @@ static void init() { addHyphenator("eu", 2, 2); // Basque addHyphenator("fr", 2, 3); // French addHyphenator("ga", 2, 3); // Irish + addHyphenator("gl", 2, 2); // Galician addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi addHyphenator("hr", 2, 2); // Croatian @@ -107,8 +109,10 @@ static void init() { // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation. // Going with a more conservative value of (2, 2) for now. addHyphenator("hy", 2, 2); // Armenian + addHyphenator("it", 2, 2); // Italian addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada addHyphenator("la", 2, 2); // Latin + addHyphenator("lt", 2, 2); // Lithuanian addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi @@ -121,6 +125,7 @@ static void init() { addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu addHyphenator("tk", 2, 2); // Turkmen + addHyphenator("uk", 2, 2); // Ukrainian addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script // Following two hyphenators do not have pattern files but there is some special logic based on diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index b40657835220..f76b211d7d7b 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -716,8 +716,6 @@ message GlobalSettingsProto { optional SettingProto nr_nsa_tracking_screen_off_mode = 153 [ (android.privacy).dest = DEST_AUTOMATIC ]; - optional SettingProto nsd_on = 83 [ (android.privacy).dest = DEST_AUTOMATIC ]; - message Ntp { option (android.msg_privacy).dest = DEST_EXPLICIT; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e2a2ac64e780..d0fee11c5f26 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1489,8 +1489,26 @@ android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_bodySensors" android:description="@string/permdesc_bodySensors" + android:backgroundPermission="android.permission.BODY_SENSORS_BACKGROUND" android:protectionLevel="dangerous" /> + <!-- Allows an application to access data from sensors that the user uses to measure what is + happening inside their body, such as heart rate. If you're requesting this permission, you + must also request {@link #BODY_SENSORS}. Requesting this permission by itself doesn't give + you Body sensors access. + <p>Protection level: dangerous + + <p> This is a hard restricted permission which cannot be held by an app until + the installer on record whitelists the permission. For more details see + {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. + --> + <permission android:name="android.permission.BODY_SENSORS_BACKGROUND" + android:permissionGroup="android.permission-group.UNDEFINED" + android:label="@string/permlab_bodySensors_background" + android:description="@string/permdesc_bodySensors_background" + android:protectionLevel="dangerous" + android:permissionFlags="hardRestricted" /> + <!-- Allows an app to use fingerprint hardware. <p>Protection level: normal @deprecated Applications should request {@link @@ -6071,6 +6089,13 @@ <permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES" android:protectionLevel="signature|role" /> + <!-- @SystemApi Allows an app to read whether SafetyCenter is enabled/disabled. + <p>Protection level: signature|privileged + @hide + --> + <permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" + android:protectionLevel="signature|privileged" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 8aa92018aa7c..bfc1c83e8fc5 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8778,6 +8778,14 @@ <attr name="hotwordDetectionService" format="string" /> </declare-styleable> + <!-- Use <code>game-service</code> as the root tag of the XML resource that + describes a GameService. + Described here are the attributes that can be included in that tag. --> + <declare-styleable name="GameService"> + <!-- The service that hosts active game sessions. This is required. --> + <attr name="gameSessionService" format="string" /> + </declare-styleable> + <!-- Use <code>voice-enrollment-application</code> as the root tag of the XML resource that escribes the supported keyphrases (hotwords) by the enrollment application. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index be32c42eb30d..bd0604e03fee 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5550,4 +5550,7 @@ That button will fire an intent targeted for this package with the mentioned action. When this resource is empty, that button will not be shown. --> <string name="config_supervisedUserCreationPackage" translatable="false"></string> + + <!-- Determines whether SafetyCenter feature is enabled. --> + <bool name="config_enableSafetyCenter">true</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 3d3c86006871..bc127d9c1d06 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3250,6 +3250,8 @@ <public name="supportedTypes" /> <public name="resetEnabledSettingsOnAppDataCleared" /> <public name="supportsStylusHandwriting" /> + <!-- @hide @SystemApi --> + <public name="gameSessionService" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b16e462192f5..1f5f18914daa 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1237,8 +1237,11 @@ <string name="permlab_bodySensors">access body sensors (like heart rate monitors) </string> <!-- Description of the body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] --> - <string name="permdesc_bodySensors" product="default">Allows the app to access data from sensors - that monitor your physical condition, such as your heart rate.</string> + <string name="permdesc_bodySensors" product="default">Access to data from body sensors such as heart rate, temperature, blood oxygen percentage, etc.</string> + <!-- Title of the background body sensors permission, listed so the user can decide whether to allow the application to access body sensor data in the background. [CHAR LIMIT=80] --> + <string name="permlab_bodySensors_background">access body sensors (like heart rate monitors) while in the background</string> + <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] --> + <string name="permdesc_bodySensors_background" product="default">Access to data from body sensors such as heart rate, temperature, blood oxygen percentage, etc. while in the background.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readCalendar">Read calendar events and details</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 527865f77257..7687b93cf760 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4634,4 +4634,6 @@ <java-symbol type="string" name="config_systemGameService" /> <java-symbol type="string" name="config_supervisedUserCreationPackage"/> + + <java-symbol type="bool" name="config_enableSafetyCenter" /> </resources> diff --git a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt index d936cad15689..121caef87f6f 100644 --- a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt +++ b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt @@ -16,6 +16,10 @@ package android.net +import android.net.NetworkTemplate.MATCH_BLUETOOTH +import android.net.NetworkTemplate.MATCH_ETHERNET +import android.net.NetworkTemplate.MATCH_MOBILE +import android.net.NetworkTemplate.MATCH_WIFI import android.text.format.Time.TIMEZONE_UTC import androidx.test.runner.AndroidJUnit4 import org.junit.Test @@ -24,6 +28,8 @@ import java.io.ByteArrayInputStream import java.io.DataInputStream import java.time.ZoneId import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue private const val TEST_IMSI1 = "TESTIMSI1" private const val TEST_SSID1 = "TESTISSID1" @@ -53,4 +59,26 @@ class NetworkPolicyTest { val restored = NetworkPolicy.getNetworkPolicyFromBackup(stream) assertEquals(policy, restored) } + + @Test + fun testIsTemplatePersistable() { + listOf(MATCH_MOBILE, MATCH_WIFI).forEach { + // Verify wildcard templates cannot be persistable. + assertFalse(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it).build())) + + // Verify mobile/wifi templates can be persistable if the Subscriber Id is supplied. + assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it) + .setSubscriberIds(setOf(TEST_IMSI1)).build())) + } + + // Verify bluetooth and ethernet templates can be persistable without any other + // field is supplied. + listOf(MATCH_BLUETOOTH, MATCH_ETHERNET).forEach { + assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(it).build())) + } + + // Verify wifi template can be persistable if the Wifi Network Key is supplied. + assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(MATCH_WIFI) + .setWifiNetworkKey(TEST_SSID1).build())) + } }
\ No newline at end of file diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index c69cb4b7e302..8ed3fac85cb2 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -45,6 +45,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -80,12 +81,12 @@ import android.net.Uri; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.chooser.ChooserTarget; +import android.view.View; import androidx.annotation.CallSuper; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; -import com.android.internal.R; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -93,6 +94,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.FrameworkStatsLog; +import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -256,7 +258,7 @@ public class ChooserActivityTest { waitForIdle(); assertThat(activity.getAdapter().getCount(), is(2)); assertThat(activity.getAdapter().getServiceTargetCount(), is(0)); - onView(withId(R.id.title)).check(matches(withText("chooser test"))); + onView(withIdFromRuntimeResource("title")).check(matches(withText("chooser test"))); } @Test @@ -275,7 +277,8 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test")); waitForIdle(); - onView(withId(R.id.title)).check(matches(withText(R.string.whichSendApplication))); + onView(withIdFromRuntimeResource("title")) + .check(matches(withTextFromRuntimeResource("whichSendApplication"))); } @Test @@ -294,8 +297,8 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.title)) - .check(matches(withText(R.string.whichSendApplication))); + onView(withIdFromRuntimeResource("title")) + .check(matches(withTextFromRuntimeResource("whichSendApplication"))); } @Test @@ -314,8 +317,10 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_title)).check(matches(not(isDisplayed()))); - onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_title")) + .check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_thumbnail")) + .check(matches(not(isDisplayed()))); } @Test @@ -335,9 +340,12 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_title)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_title)).check(matches(withText(previewTitle))); - onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_title")) + .check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_title")) + .check(matches(withText(previewTitle))); + onView(withIdFromRuntimeResource("content_preview_thumbnail")) + .check(matches(not(isDisplayed()))); } @Test @@ -358,8 +366,9 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_title)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_thumbnail)).check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_title")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_thumbnail")) + .check(matches(not(isDisplayed()))); } @Test @@ -382,8 +391,9 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_title)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_thumbnail)).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_title")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_thumbnail")) + .check(matches(isDisplayed())); } @Test @Ignore @@ -406,7 +416,7 @@ public class ChooserActivityTest { waitForIdle(); assertThat(activity.getAdapter().getCount(), is(2)); - onView(withId(R.id.profile_button)).check(doesNotExist()); + onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist()); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> { @@ -536,8 +546,8 @@ public class ChooserActivityTest { waitForIdle(); assertThat(activity.isFinishing(), is(false)); - onView(withId(R.id.empty)).check(matches(isDisplayed())); - onView(withId(R.id.profile_pager)).check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("empty")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("profile_pager")).check(matches(not(isDisplayed()))); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> wrapper.getAdapter().handlePackagesChanged() ); @@ -700,8 +710,8 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed())); - onView(withId(R.id.chooser_copy_button)).perform(click()); + onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click()); ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( Context.CLIPBOARD_SERVICE); ClipData clipData = clipboard.getPrimaryClip(); @@ -729,8 +739,8 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed())); - onView(withId(R.id.chooser_copy_button)).perform(click()); + onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click()); verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture()); @@ -755,8 +765,8 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.chooser_nearby_button)).check(matches(isDisplayed())); - onView(withId(R.id.chooser_nearby_button)).perform(click()); + onView(withIdFromRuntimeResource("chooser_nearby_button")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("chooser_nearby_button")).perform(click()); ChooserActivityLoggerFake logger = (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); @@ -824,8 +834,8 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.chooser_edit_button)).check(matches(isDisplayed())); - onView(withId(R.id.chooser_edit_button)).perform(click()); + onView(withIdFromRuntimeResource("chooser_edit_button")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("chooser_edit_button")).perform(click()); ChooserActivityLoggerFake logger = (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); @@ -897,10 +907,14 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_image_1_large)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_image_2_large)).check(matches(not(isDisplayed()))); - onView(withId(R.id.content_preview_image_2_small)).check(matches(not(isDisplayed()))); - onView(withId(R.id.content_preview_image_3_small)).check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_image_1_large")) + .check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_image_2_large")) + .check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_image_2_small")) + .check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_image_3_small")) + .check(matches(not(isDisplayed()))); } @Test @@ -929,10 +943,14 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_image_1_large)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_image_2_large)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_image_2_small)).check(matches(not(isDisplayed()))); - onView(withId(R.id.content_preview_image_3_small)).check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_image_1_large")) + .check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_image_2_large")) + .check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_image_2_small")) + .check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_image_3_small")) + .check(matches(not(isDisplayed()))); } @Test @@ -964,10 +982,14 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_image_1_large)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_image_2_large)).check(matches(not(isDisplayed()))); - onView(withId(R.id.content_preview_image_2_small)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_image_3_small)).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_image_1_large")) + .check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_image_2_large")) + .check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("content_preview_image_2_small")) + .check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_image_3_small")) + .check(matches(isDisplayed())); } @Test @@ -1120,9 +1142,11 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")) + .check(matches(withText("app.pdf"))); + onView(withIdFromRuntimeResource("content_preview_file_icon")) + .check(matches(isDisplayed())); } @@ -1150,9 +1174,12 @@ public class ChooserActivityTest { .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf + 2 files"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")) + .check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")) + .check(matches(withText("app.pdf + 2 files"))); + onView(withIdFromRuntimeResource("content_preview_file_icon")) + .check(matches(isDisplayed())); } @Test @@ -1179,9 +1206,11 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")) + .check(matches(withText("app.pdf"))); + onView(withIdFromRuntimeResource("content_preview_file_icon")) + .check(matches(isDisplayed())); } @Test @@ -1215,9 +1244,11 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf + 1 file"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("content_preview_filename")) + .check(matches(withText("app.pdf + 1 file"))); + onView(withIdFromRuntimeResource("content_preview_file_icon")) + .check(matches(isDisplayed())); } @Test @@ -1296,7 +1327,10 @@ public class ChooserActivityTest { ChooserActivityOverrideData .getInstance() .resources - .getString(R.string.config_defaultAppPredictionService)) + .getString( + getRuntimeResourceId( + "config_defaultAppPredictionService", + "string"))) .thenReturn("ComponentNameThatDoesNotExist"); assertThat(activity.isAppPredictionServiceAvailable(), is(false)); @@ -1544,7 +1578,8 @@ public class ChooserActivityTest { ChooserActivityOverrideData .getInstance() .resources - .getInteger(R.integer.config_maxShortcutTargetsPerApp)) + .getInteger( + getRuntimeResourceId("config_maxShortcutTargetsPerApp", "integer"))) .thenReturn(1); Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed @@ -1615,7 +1650,8 @@ public class ChooserActivityTest { ChooserActivityOverrideData .getInstance() .resources - .getInteger(R.integer.config_maxShortcutTargetsPerApp)) + .getInteger( + getRuntimeResourceId("config_maxShortcutTargetsPerApp", "integer"))) .thenReturn(1); Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed @@ -1787,7 +1823,7 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.tabs)).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("tabs")).check(matches(isDisplayed())); } @Test @@ -1800,7 +1836,7 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.tabs)).check(matches(not(isDisplayed()))); + onView(withIdFromRuntimeResource("tabs")).check(matches(not(isDisplayed()))); } @Test @@ -1825,7 +1861,7 @@ public class ChooserActivityTest { waitForIdle(); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets)); assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); @@ -1848,7 +1884,7 @@ public class ChooserActivityTest { final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); @@ -1875,7 +1911,7 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); // wait for the share sheet to expand Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); @@ -1906,12 +1942,12 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_cross_profile_blocked)) + onView(withTextFromRuntimeResource("resolver_cross_profile_blocked")) .check(matches(isDisplayed())); } @@ -1932,12 +1968,12 @@ public class ChooserActivityTest { ResolverActivity.ENABLE_TABBED_VIEW = true; mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_turn_on_work_apps)) + onView(withTextFromRuntimeResource("resolver_turn_on_work_apps")) .check(matches(isDisplayed())); } @@ -1956,12 +1992,12 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available)) + onView(withTextFromRuntimeResource("resolver_no_work_apps_available")) .check(matches(isDisplayed())); } @@ -1982,12 +2018,12 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_cross_profile_blocked)) + onView(withTextFromRuntimeResource("resolver_cross_profile_blocked")) .check(matches(isDisplayed())); } @@ -2007,12 +2043,12 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available)) + onView(withTextFromRuntimeResource("resolver_no_work_apps_available")) .check(matches(isDisplayed())); } @@ -2036,7 +2072,7 @@ public class ChooserActivityTest { waitForIdle(); assertThat(activity.getAdapter().getCount(), is(2)); - onView(withId(R.id.profile_button)).check(doesNotExist()); + onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist()); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> { @@ -2265,8 +2301,8 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); - onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed())); - onView(withId(R.id.chooser_copy_button)).perform(click()); + onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed())); + onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click()); ChooserActivityLoggerFake logger = (ChooserActivityLoggerFake) activity.getChooserActivityLogger(); @@ -2329,9 +2365,9 @@ public class ChooserActivityTest { final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_personal_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_personal_tab")).perform(click()); waitForIdle(); ChooserActivityLoggerFake logger = @@ -2576,12 +2612,12 @@ public class ChooserActivityTest { mActivityRule.launchActivity(chooserIntent); waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_cross_profile_blocked)) + onView(withTextFromRuntimeResource("resolver_cross_profile_blocked")) .check(matches(isDisplayed())); } @@ -2610,12 +2646,12 @@ public class ChooserActivityTest { mActivityRule.launchActivity(chooserIntent); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available)) + onView(withTextFromRuntimeResource("resolver_no_work_apps_available")) .check(matches(isDisplayed())); } @@ -2675,9 +2711,9 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); assertFalse("Direct share targets were queried on a paused work profile", @@ -2707,9 +2743,9 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); assertFalse("Direct share targets were queried on a locked work profile user", @@ -2734,9 +2770,8 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); final IChooserWrapper wrapper = (IChooserWrapper) activity; waitForIdle(); - onView(withId(R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withIdFromRuntimeResource("contentPanel")).perform(swipeUp()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); assertEquals(3, wrapper.getWorkListAdapter().getCount()); @@ -2765,9 +2800,9 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); assertFalse("Direct share targets were queried on a locked work profile user", @@ -2792,9 +2827,9 @@ public class ChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); final IChooserWrapper wrapper = (IChooserWrapper) activity; waitForIdle(); - onView(withId(R.id.contentPanel)) + onView(withIdFromRuntimeResource("contentPanel")) .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); + onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click()); waitForIdle(); assertEquals(3, wrapper.getWorkListAdapter().getCount()); @@ -3043,4 +3078,27 @@ public class ChooserActivityTest { eq(UserHandle.SYSTEM))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); } + + private Matcher<View> withIdFromRuntimeResource(String id) { + return withId(getRuntimeResourceId(id, "id")); + } + + private Matcher<View> withTextFromRuntimeResource(String id) { + return withText(getRuntimeResourceId(id, "string")); + } + + // ChooserWrapperActivity inherits from the framework ChooserActivity, so if the framework + // resources have been updated since the framework was last built/pushed, the inherited behavior + // (which is the focus of our testing) will still be implemented in terms of the old resource + // IDs; then when we try to assert those IDs in tests (e.g. `onView(withText(R.string.foo))`), + // the expected values won't match. The tests can instead call this method (with the same + // general semantics as Resources#getIdentifier() e.g. `getRuntimeResourceId("foo", "string")`) + // to refer to the resource by that name in the runtime chooser, regardless of whether the + // framework code on the device is up-to-date. + // TODO: is there a better way to do this? (Other than abandoning inheritance-based DI wrapper?) + private int getRuntimeResourceId(String name, String defType) { + int id = mActivityRule.getActivity().getResources().getIdentifier(name, defType, "android"); + assertThat(id, greaterThan(0)); + return id; + } } diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 0b8dc3fe0dec..92fca3661fbc 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -223,6 +223,10 @@ targetSdk="29"> <new-permission name="android.permission.ACCESS_BACKGROUND_LOCATION" /> </split-permission> + <split-permission name="android.permission.BODY_SENSORS" + targetSdk="33"> + <new-permission name="android.permission.BODY_SENSORS_BACKGROUND" /> + </split-permission> <split-permission name="android.permission.READ_EXTERNAL_STORAGE" targetSdk="29"> <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index e61a665307f8..f17fa3b17fe5 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -520,6 +520,8 @@ applications that come with the platform <permission name="android.permission.LOCK_DEVICE" /> <!-- Permission required for CTS test - CtsSafetyCenterTestCases --> <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" /> + <!-- Permission required for CTS test - CtsSafetyCenterTestCases --> + <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> <!-- Permission required for CTS test - CommunalManagerTest --> <permission name="android.permission.WRITE_COMMUNAL_STATE" /> <permission name="android.permission.READ_COMMUNAL_STATE" /> diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index b77865f423c6..fc7f84c3c83e 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -118,13 +118,13 @@ public final class Outline { /** * Returns whether the outline can be used to clip a View. * <p> - * Currently, only Outlines that can be represented as a rectangle, circle, - * or round rect support clipping. + * As of API 33, all Outline shapes support clipping. Prior to API 33, only Outlines that + * could be represented as a rectangle, circle, or round rect supported clipping. * * @see android.view.View#setClipToOutline(boolean) */ public boolean canClip() { - return mMode != MODE_PATH; + return true; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index a006f308d694..3de59b48bcf0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -25,6 +25,7 @@ import android.app.ActivityTaskManager; import android.app.TaskInfo; import android.content.Context; import android.os.RemoteException; +import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -316,6 +317,7 @@ public class RecentTasksController implements TaskStackListenerCallback, (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) .toArray(new GroupedRecentTaskInfo[0]), true /* blocking */); + Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0]); return out[0]; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index a91dfe1c13e2..448773ae9ea2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -76,12 +76,6 @@ public interface SplitScreen { } /** - * Called when the keyguard occluded state changes. - * @param occluded Indicates if the keyguard is now occluded. - */ - void onKeyguardOccludedChanged(boolean occluded); - - /** * Called when the visibility of the keyguard changes. * @param showing Indicates if the keyguard is now visible. */ @@ -90,9 +84,6 @@ public interface SplitScreen { /** Called when device waking up finished. */ void onFinishedWakingUp(); - /** Called when device going to sleep finished. */ - void onFinishedGoingToSleep(); - /** Get a string representation of a stage type */ static String stageTypeToString(@StageType int stage) { switch (stage) { 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 18cabd3ddb9a..1f49a4cc3ab9 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 @@ -249,10 +249,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason); } - public void onKeyguardOccludedChanged(boolean occluded) { - mStageCoordinator.onKeyguardOccludedChanged(occluded); - } - public void onKeyguardVisibilityChanged(boolean showing) { mStageCoordinator.onKeyguardVisibilityChanged(showing); } @@ -261,10 +257,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.onFinishedWakingUp(); } - public void onFinishedGoingToSleep() { - mStageCoordinator.onFinishedGoingToSleep(); - } - public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide); } @@ -491,13 +483,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override - public void onKeyguardOccludedChanged(boolean occluded) { - mMainExecutor.execute(() -> { - SplitScreenController.this.onKeyguardOccludedChanged(occluded); - }); - } - - @Override public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) { if (mExecutors.containsKey(listener)) return; @@ -538,13 +523,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, SplitScreenController.this.onFinishedWakingUp(); }); } - - @Override - public void onFinishedGoingToSleep() { - mMainExecutor.execute(() -> { - SplitScreenController.this.onFinishedGoingToSleep(); - }); - } } /** 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 d2176866283b..2385ec90ab23 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 @@ -46,7 +46,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; -import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; import static com.android.wm.shell.transition.Transitions.isClosingType; import static com.android.wm.shell.transition.Transitions.isOpeningType; @@ -157,8 +156,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // and exit, since exit itself can trigger a number of changes that update the stages. private boolean mShouldUpdateRecents; private boolean mExitSplitScreenOnHide; - private boolean mKeyguardOccluded; - private boolean mDeviceSleep; /** The target stage to dismiss to when unlock after folded. */ @StageType @@ -171,6 +168,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // properly for the animation itself. mSplitLayout.release(); mSplitLayout.resetDividerPosition(); + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } }; @@ -554,29 +552,54 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTaskOrganizer.applyTransaction(wct); } - void onKeyguardOccludedChanged(boolean occluded) { - // Do not exit split directly, because it needs to wait for task info update to determine - // which task should remain on top after split dismissed. - mKeyguardOccluded = occluded; - } - void onKeyguardVisibilityChanged(boolean showing) { - if (!showing && mMainStage.isActive() - && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { - exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, - EXIT_REASON_DEVICE_FOLDED); + if (!mMainStage.isActive()) { + return; + } + + if (ENABLE_SHELL_TRANSITIONS) { + // Update divider visibility so it won't float on top of keyguard. + setDividerVisibility(!showing, null /* transaction */); + } + + if (!showing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { + if (ENABLE_SHELL_TRANSITIONS) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); + mSplitTransitions.startDismissTransition(null /* transition */, wct, this, + mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); + } else { + exitSplitScreen( + mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, + EXIT_REASON_DEVICE_FOLDED); + } } } void onFinishedWakingUp() { - if (mMainStage.isActive()) { - exitSplitScreenIfKeyguardOccluded(); + if (!mMainStage.isActive()) { + return; } - mDeviceSleep = false; - } - void onFinishedGoingToSleep() { - mDeviceSleep = true; + // Check if there's only one stage visible while keyguard occluded. + final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible; + final boolean oneStageVisible = + mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible; + if (oneStageVisible) { + // Dismiss split because there's show-when-locked activity showing on top of keyguard. + // Also make sure the task contains show-when-locked activity remains on top after split + // dismissed. + if (!ENABLE_SHELL_TRANSITIONS) { + final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; + exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + } else { + final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(dismissTop, wct); + mSplitTransitions.startDismissTransition(null /* transition */, wct, this, + dismissTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + } + } } void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { @@ -607,19 +630,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, applyExitSplitScreen(childrenToTop, wct, exitReason); } - private void exitSplitScreenIfKeyguardOccluded() { - final boolean mainStageVisible = mMainStageListener.mVisible; - final boolean oneStageVisible = mainStageVisible ^ mSideStageListener.mVisible; - if (mDeviceSleep && mKeyguardOccluded && oneStageVisible) { - // Only the stages include show-when-locked activity is visible while keyguard occluded. - // Dismiss split because there's show-when-locked activity showing on top of keyguard. - // Also make sure the task contains show-when-locked activity remains on top after split - // dismissed. - final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; - exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); - } - } - private void applyExitSplitScreen(StageTaskListener childrenToTop, WindowContainerTransaction wct, @ExitReason int exitReason) { mRecentTasks.ifPresent(recentTasks -> { @@ -669,6 +679,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, case EXIT_REASON_APP_FINISHED: // One of the children enters PiP case EXIT_REASON_CHILD_TASK_ENTER_PIP: + // One of the apps occludes lock screen. + case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP: + // User has unlocked the device after folded + case EXIT_REASON_DEVICE_FOLDED: return true; default: return false; @@ -828,31 +842,27 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onStageVisibilityChanged(StageListenerImpl stageListener) { final boolean sideStageVisible = mSideStageListener.mVisible; final boolean mainStageVisible = mMainStageListener.mVisible; - final boolean bothStageVisible = sideStageVisible && mainStageVisible; - final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible; - final boolean sameVisibility = sideStageVisible == mainStageVisible; - if (bothStageInvisible) { + // Wait for both stages having the same visibility to prevent causing flicker. + if (mainStageVisible != sideStageVisible) { + return; + } + + if (!mainStageVisible) { + // Both stages are not visible, check if it needs to dismiss split screen. if (mExitSplitScreenOnHide - // Don't dismiss staged split when both stages are not visible due to sleeping - // display, like the cases keyguard showing or screen off. + // Don't dismiss split screen when both stages are not visible due to sleeping + // display. || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) { - // Don't dismiss staged split when both stages are not visible due to sleeping display, - // like the cases keyguard showing or screen off. exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME); } } - exitSplitScreenIfKeyguardOccluded(); mSyncQueue.runInSync(t -> { - // Same above, we only set root tasks and divider leash visibility when both stage - // change to visible or invisible to avoid flicker. - if (sameVisibility) { - t.setVisibility(mSideStage.mRootLeash, bothStageVisible) - .setVisibility(mMainStage.mRootLeash, bothStageVisible); - setDividerVisibility(bothStageVisible, t); - } + t.setVisibility(mSideStage.mRootLeash, sideStageVisible) + .setVisibility(mMainStage.mRootLeash, mainStageVisible); + setDividerVisibility(mainStageVisible, t); }); } @@ -1374,11 +1384,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSplitsVisible(false); // Wait until after animation to update divider - if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { - // Reset crops so they don't interfere with subsequent launches - t.setWindowCrop(mMainStage.mRootLeash, null); - t.setWindowCrop(mSideStage.mRootLeash, null); - } + // Reset crops so they don't interfere with subsequent launches + t.setWindowCrop(mMainStage.mRootLeash, null); + t.setWindowCrop(mSideStage.mRootLeash, null); if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { logExit(dismissTransition.mReason); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 324b8b53e433..0c5ec6e465cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -18,6 +18,7 @@ package com.android.wm.shell.transition; import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.app.ActivityOptions.ANIM_FROM_STYLE; import static android.app.ActivityOptions.ANIM_NONE; import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; import static android.app.ActivityOptions.ANIM_SCALE_UP; @@ -509,60 +510,70 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) { // This received a transferred starting window, so don't animate return null; - } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation - : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation); - } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation - : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation); - } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation - : R.styleable.WindowAnimation_wallpaperOpenExitAnimation); - } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation - : R.styleable.WindowAnimation_wallpaperCloseExitAnimation); - } else if (type == TRANSIT_OPEN) { - if (isTask) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_taskOpenEnterAnimation - : R.styleable.WindowAnimation_taskOpenExitAnimation); - } else { - if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) { - a = mTransitionAnimation.loadDefaultAnimationRes( - R.anim.activity_translucent_open_enter); + } else { + int animAttr = 0; + boolean translucent = false; + if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { + animAttr = enter + ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation + : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; + } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) { + animAttr = enter + ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation + : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation; + } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) { + animAttr = enter + ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation + : R.styleable.WindowAnimation_wallpaperOpenExitAnimation; + } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) { + animAttr = enter + ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation + : R.styleable.WindowAnimation_wallpaperCloseExitAnimation; + } else if (type == TRANSIT_OPEN) { + if (isTask) { + animAttr = enter + ? R.styleable.WindowAnimation_taskOpenEnterAnimation + : R.styleable.WindowAnimation_taskOpenExitAnimation; } else { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter + if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) { + translucent = true; + } + animAttr = enter ? R.styleable.WindowAnimation_activityOpenEnterAnimation - : R.styleable.WindowAnimation_activityOpenExitAnimation); + : R.styleable.WindowAnimation_activityOpenExitAnimation; } - } - } else if (type == TRANSIT_TO_FRONT) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_taskToFrontEnterAnimation - : R.styleable.WindowAnimation_taskToFrontExitAnimation); - } else if (type == TRANSIT_CLOSE) { - if (isTask) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_taskCloseEnterAnimation - : R.styleable.WindowAnimation_taskCloseExitAnimation); - } else { - if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { - a = mTransitionAnimation.loadDefaultAnimationRes( - R.anim.activity_translucent_close_exit); + } else if (type == TRANSIT_TO_FRONT) { + animAttr = enter + ? R.styleable.WindowAnimation_taskToFrontEnterAnimation + : R.styleable.WindowAnimation_taskToFrontExitAnimation; + } else if (type == TRANSIT_CLOSE) { + if (isTask) { + animAttr = enter + ? R.styleable.WindowAnimation_taskCloseEnterAnimation + : R.styleable.WindowAnimation_taskCloseExitAnimation; } else { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter + if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) { + translucent = true; + } + animAttr = enter ? R.styleable.WindowAnimation_activityCloseEnterAnimation - : R.styleable.WindowAnimation_activityCloseExitAnimation); + : R.styleable.WindowAnimation_activityCloseExitAnimation; + } + } else if (type == TRANSIT_TO_BACK) { + animAttr = enter + ? R.styleable.WindowAnimation_taskToBackEnterAnimation + : R.styleable.WindowAnimation_taskToBackExitAnimation; + } + + if (animAttr != 0) { + if (overrideType == ANIM_FROM_STYLE && canCustomContainer) { + a = mTransitionAnimation + .loadAnimationAttr(options.getPackageName(), options.getAnimations(), + animAttr, translucent); + } else { + a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr); } } - } else if (type == TRANSIT_TO_BACK) { - a = mTransitionAnimation.loadDefaultAnimationAttr(enter - ? R.styleable.WindowAnimation_taskToBackEnterAnimation - : R.styleable.WindowAnimation_taskToBackExitAnimation); } if (a != null) { diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h index 2eb2c7c7e299..e16fd8c38c75 100644 --- a/libs/hwui/Outline.h +++ b/libs/hwui/Outline.h @@ -88,14 +88,10 @@ public: bool getShouldClip() const { return mShouldClip; } - bool willClip() const { - // only round rect outlines can be used for clipping - return mShouldClip && (mType == Type::RoundRect); - } + bool willClip() const { return mShouldClip; } - bool willRoundRectClip() const { - // only round rect outlines can be used for clipping - return willClip() && MathUtils::isPositive(mRadius); + bool willComplexClip() const { + return mShouldClip && (mType != Type::RoundRect || MathUtils::isPositive(mRadius)); } bool getAsRoundRect(Rect* outRect, float* outRadius) const { diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp index dd8439647fd3..3d0ca0a10851 100644 --- a/libs/hwui/ProfileData.cpp +++ b/libs/hwui/ProfileData.cpp @@ -137,6 +137,7 @@ void ProfileData::dump(int fd) const { histogramGPUForEach([fd](HistogramEntry entry) { dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount); }); + dprintf(fd, "\n"); } uint32_t ProfileData::findPercentile(int percentile) const { diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index cd622eba37b6..064ba7aee107 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -165,11 +165,11 @@ public: bool prepareForFunctorPresence(bool willHaveFunctor, bool ancestorDictatesFunctorsNeedLayer) { // parent may have already dictated that a descendant layer is needed bool functorsNeedLayer = - ancestorDictatesFunctorsNeedLayer - || CC_UNLIKELY(isClipMayBeComplex()) + ancestorDictatesFunctorsNeedLayer || + CC_UNLIKELY(isClipMayBeComplex()) // Round rect clipping forces layer for functors - || CC_UNLIKELY(getOutline().willRoundRectClip()) || + || CC_UNLIKELY(getOutline().willComplexClip()) || CC_UNLIKELY(getRevealClip().willClip()) // Complex matrices forces layer, due to stencil clipping diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 48145d2331ee..507d3dcdcde9 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -88,6 +88,10 @@ static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* if (pendingClip) { canvas->clipRect(*pendingClip); } + const SkPath* path = outline.getPath(); + if (path) { + canvas->clipPath(*path, SkClipOp::kIntersect, true); + } return; } diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp new file mode 100644 index 000000000000..1e343c1dd283 --- /dev/null +++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vector> + +#include "TestSceneBase.h" + +class PathClippingAnimation : public TestScene { +public: + int mSpacing, mSize; + bool mClip, mAnimateClip; + int mMaxCards; + std::vector<sp<RenderNode> > cards; + + PathClippingAnimation(int spacing, int size, bool clip, bool animateClip, int maxCards) + : mSpacing(spacing) + , mSize(size) + , mClip(clip) + , mAnimateClip(animateClip) + , mMaxCards(maxCards) {} + + PathClippingAnimation(int spacing, int size, bool clip, bool animateClip) + : PathClippingAnimation(spacing, size, clip, animateClip, INT_MAX) {} + + void createContent(int width, int height, Canvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver); + canvas.enableZ(true); + int ci = 0; + int numCards = 0; + + for (int x = 0; x < width; x += mSpacing) { + for (int y = 0; y < height; y += mSpacing) { + auto color = BrightColors[ci++ % BrightColorsCount]; + auto card = TestUtils::createNode( + x, y, x + mSize, y + mSize, [&](RenderProperties& props, Canvas& canvas) { + canvas.drawColor(color, SkBlendMode::kSrcOver); + if (mClip) { + // Create circular path that rounds around the inside of all + // four corners of the given square defined by mSize*mSize + SkPath path = setPath(mSize); + props.mutableOutline().setPath(&path, 1); + props.mutableOutline().setShouldClip(true); + } + }); + canvas.drawRenderNode(card.get()); + cards.push_back(card); + ++numCards; + if (numCards >= mMaxCards) { + break; + } + } + if (numCards >= mMaxCards) { + break; + } + } + + canvas.enableZ(false); + } + + SkPath setPath(int size) { + SkPath path; + path.moveTo(0, size / 2); + path.cubicTo(0, size * .75, size * .25, size, size / 2, size); + path.cubicTo(size * .75, size, size, size * .75, size, size / 2); + path.cubicTo(size, size * .25, size * .75, 0, size / 2, 0); + path.cubicTo(size / 4, 0, 0, size / 4, 0, size / 2); + return path; + } + + void doFrame(int frameNr) override { + int curFrame = frameNr % 50; + if (curFrame > 25) curFrame = 50 - curFrame; + for (auto& card : cards) { + if (mAnimateClip) { + SkPath path = setPath(mSize - curFrame); + card->mutateStagingProperties().mutableOutline().setPath(&path, 1); + } + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::DISPLAY_LIST); + } + } +}; + +static TestScene::Registrar _PathClippingUnclipped(TestScene::Info{ + "pathClipping-unclipped", "Multiple RenderNodes, unclipped.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(80), false, false); + }}); + +static TestScene::Registrar _PathClippingUnclippedSingle(TestScene::Info{ + "pathClipping-unclippedsingle", "A single RenderNode, unclipped.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(80), false, false, 1); + }}); + +static TestScene::Registrar _PathClippingUnclippedSingleLarge(TestScene::Info{ + "pathClipping-unclippedsinglelarge", "A single large RenderNode, unclipped.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(350), false, false, 1); + }}); + +static TestScene::Registrar _PathClippingClipped80(TestScene::Info{ + "pathClipping-clipped80", "Multiple RenderNodes, clipped by paths.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(80), true, false); + }}); + +static TestScene::Registrar _PathClippingClippedSingle(TestScene::Info{ + "pathClipping-clippedsingle", "A single RenderNode, clipped by a path.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(80), true, false, 1); + }}); + +static TestScene::Registrar _PathClippingClippedSingleLarge(TestScene::Info{ + "pathClipping-clippedsinglelarge", "A single large RenderNode, clipped by a path.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(350), true, false, 1); + }}); + +static TestScene::Registrar _PathClippingAnimated(TestScene::Info{ + "pathClipping-animated", + "Multiple RenderNodes, clipped by paths which are being altered every frame.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(80), true, true); + }}); + +static TestScene::Registrar _PathClippingAnimatedSingle(TestScene::Info{ + "pathClipping-animatedsingle", + "A single RenderNode, clipped by a path which is being altered every frame.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(80), true, true, 1); + }}); + +static TestScene::Registrar _PathClippingAnimatedSingleLarge(TestScene::Info{ + "pathClipping-animatedsinglelarge", + "A single large RenderNode, clipped by a path which is being altered every frame.", + [](const TestScene::Options&) -> test::TestScene* { + return new PathClippingAnimation(dp(100), dp(350), true, true, 1); + }}); diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp index 163745b04ed2..e9f353d887f2 100644 --- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp @@ -21,14 +21,17 @@ class RoundRectClippingAnimation : public TestScene { public: int mSpacing, mSize; + int mMaxCards; - RoundRectClippingAnimation(int spacing, int size) : mSpacing(spacing), mSize(size) {} + RoundRectClippingAnimation(int spacing, int size, int maxCards = INT_MAX) + : mSpacing(spacing), mSize(size), mMaxCards(maxCards) {} std::vector<sp<RenderNode> > cards; void createContent(int width, int height, Canvas& canvas) override { canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver); canvas.enableZ(true); int ci = 0; + int numCards = 0; for (int x = 0; x < width; x += mSpacing) { for (int y = 0; y < height; y += mSpacing) { @@ -42,6 +45,13 @@ public: }); canvas.drawRenderNode(card.get()); cards.push_back(card); + ++numCards; + if (numCards >= mMaxCards) { + break; + } + } + if (numCards >= mMaxCards) { + break; } } @@ -71,3 +81,22 @@ static TestScene::Registrar _RoundRectClippingCpu(TestScene::Info{ [](const TestScene::Options&) -> test::TestScene* { return new RoundRectClippingAnimation(dp(20), dp(20)); }}); + +static TestScene::Registrar _RoundRectClippingGrid(TestScene::Info{ + "roundRectClipping-grid", "A grid of RenderNodes with round rect clipping outlines.", + [](const TestScene::Options&) -> test::TestScene* { + return new RoundRectClippingAnimation(dp(100), dp(80)); + }}); + +static TestScene::Registrar _RoundRectClippingSingle(TestScene::Info{ + "roundRectClipping-single", "A single RenderNodes with round rect clipping outline.", + [](const TestScene::Options&) -> test::TestScene* { + return new RoundRectClippingAnimation(dp(100), dp(80), 1); + }}); + +static TestScene::Registrar _RoundRectClippingSingleLarge(TestScene::Info{ + "roundRectClipping-singlelarge", + "A single large RenderNodes with round rect clipping outline.", + [](const TestScene::Options&) -> test::TestScene* { + return new RoundRectClippingAnimation(dp(100), dp(350), 1); + }}); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 0722417c0d18..dd27cf140ca1 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6788,56 +6788,63 @@ public class AudioManager { /** * Returns a list of audio formats that corresponds to encoding formats - * supported on offload path for A2DP and LE audio playback. + * supported on offload path for A2DP playback. * - * @param deviceType Indicates the target device type {@link AudioSystem.DeviceType} * @return a list of {@link BluetoothCodecConfig} objects containing encoding formats - * supported for offload A2DP playback or a list of {@link BluetoothLeAudioCodecConfig} - * objects containing encoding formats supported for offload LE Audio playback + * supported for offload A2DP playback * @hide */ - public List<?> getHwOffloadFormatsSupportedForBluetoothMedia( - @AudioSystem.DeviceType int deviceType) { - ArrayList<Integer> formatsList = new ArrayList<Integer>(); - ArrayList<BluetoothCodecConfig> a2dpCodecConfigList = new ArrayList<BluetoothCodecConfig>(); - ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList = - new ArrayList<BluetoothLeAudioCodecConfig>(); + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public @NonNull List<BluetoothCodecConfig> getHwOffloadFormatsSupportedForA2dp() { + ArrayList<Integer> formatsList = new ArrayList<>(); + ArrayList<BluetoothCodecConfig> codecConfigList = new ArrayList<>(); - if (deviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP - && deviceType != AudioSystem.DEVICE_OUT_BLE_HEADSET) { - throw new IllegalArgumentException( - "Illegal devicetype for the getHwOffloadFormatsSupportedForBluetoothMedia"); + int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, formatsList); + if (status != AudioManager.SUCCESS) { + Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForA2DP failed:" + status); + return codecConfigList; + } + + for (Integer format : formatsList) { + int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format); + if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + codecConfigList.add(new BluetoothCodecConfig(btSourceCodec)); + } } + return codecConfigList; + } + + /** + * Returns a list of audio formats that corresponds to encoding formats + * supported on offload path for Le audio playback. + * + * @return a list of {@link BluetoothLeAudioCodecConfig} objects containing encoding formats + * supported for offload Le Audio playback + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull + public List<BluetoothLeAudioCodecConfig> getHwOffloadFormatsSupportedForLeAudio() { + ArrayList<Integer> formatsList = new ArrayList<>(); + ArrayList<BluetoothLeAudioCodecConfig> leAudioCodecConfigList = new ArrayList<>(); - int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(deviceType, - formatsList); + int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia( + AudioSystem.DEVICE_OUT_BLE_HEADSET, formatsList); if (status != AudioManager.SUCCESS) { - Log.e(TAG, "getHwOffloadFormatsSupportedForBluetoothMedia for deviceType " - + deviceType + " failed:" + status); - return a2dpCodecConfigList; + Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForLeAudio failed:" + status); + return leAudioCodecConfigList; } - if (deviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - for (Integer format : formatsList) { - int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format); - if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - a2dpCodecConfigList.add(new BluetoothCodecConfig(btSourceCodec)); - } + for (Integer format : formatsList) { + int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format); + if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder() + .setCodecType(btLeAudioCodec) + .build()); } - return a2dpCodecConfigList; - } else if (deviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) { - for (Integer format : formatsList) { - int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format); - if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder() - .setCodecType(btLeAudioCodec) - .build()); - } - } - return leAudioCodecConfigList; } - Log.e(TAG, "Input deviceType " + deviceType + " doesn't support."); - return a2dpCodecConfigList; + return leAudioCodecConfigList; } // Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java index dcd4dce5f3eb..ec56d614f2b5 100644 --- a/media/java/android/media/MediaActionSound.java +++ b/media/java/android/media/MediaActionSound.java @@ -16,8 +16,11 @@ package android.media; -import android.media.AudioManager; +import android.content.Context; import android.media.SoundPool; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Log; /** @@ -104,6 +107,26 @@ public class MediaActionSound { private static final int STATE_LOADING_PLAY_REQUESTED = 2; private static final int STATE_LOADED = 3; + /** + * <p>Returns true if the application must play the shutter sound in accordance + * to certain regional restrictions. </p> + * + * <p>If this method returns true, applications are strongly recommended to use + * MediaActionSound.play(SHUTTER_CLICK) or START_VIDEO_RECORDING whenever it captures + * images or video to storage or sends them over the network.</p> + */ + public static boolean mustPlayShutterSound() { + boolean result = false; + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + IAudioService audioService = IAudioService.Stub.asInterface(b); + try { + result = audioService.isCameraSoundForced(); + } catch (RemoteException e) { + Log.e(TAG, "audio service is unavailable for queries, defaulting to false"); + } + return result; + } + private class SoundState { public final int name; public int id; diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 255b391b2259..41f926520a13 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -1577,6 +1577,20 @@ public class Tuner implements AutoCloseable { } } + private void onDvbtCellIdsReported(int[] dvbtCellIds) { + synchronized (mScanCallbackLock) { + if (mScanCallbackExecutor != null && mScanCallback != null) { + mScanCallbackExecutor.execute(() -> { + synchronized (mScanCallbackLock) { + if (mScanCallback != null) { + mScanCallback.onDvbtCellIdsReported(dvbtCellIds); + } + } + }); + } + } + } + /** * Opens a filter object based on the given types and buffer size. * diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index 92eafec6a13a..8cedd04a8b89 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -53,7 +53,8 @@ public class FrontendStatus { FRONTEND_STATUS_TYPE_MODULATIONS_EXT, FRONTEND_STATUS_TYPE_ROLL_OFF, FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR, FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE, - FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS}) + FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS, + FRONTEND_STATUS_TYPE_DVBT_CELL_IDS}) @Retention(RetentionPolicy.SOURCE) public @interface FrontendStatusType {} @@ -260,6 +261,12 @@ public class FrontendStatus { public static final int FRONTEND_STATUS_TYPE_STREAM_IDS = android.hardware.tv.tuner.FrontendStatusType.STREAM_ID_LIST; + /** + * DVB-T Cell IDs. + */ + public static final int FRONTEND_STATUS_TYPE_DVBT_CELL_IDS = + android.hardware.tv.tuner.FrontendStatusType.DVBT_CELL_IDS; + /** @hide */ @IntDef(value = { AtscFrontendSettings.MODULATION_UNDEFINED, @@ -500,6 +507,7 @@ public class FrontendStatus { private Integer mIsdbtMode; private Integer mIsdbtPartialReceptionFlag; private int[] mStreamIds; + private int[] mDvbtCellIds; // Constructed and fields set by JNI code. private FrontendStatus() { @@ -1052,6 +1060,24 @@ public class FrontendStatus { } /** + * Gets DVB-T cell ids. + * + * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL + * doesn't return cell ids will throw IllegalStateException. Use + * {@link TunerVersionChecker#getTunerVersion()} to check the version. + */ + @SuppressLint("ArrayReturn") + @NonNull + public int[] getDvbtCellIds() { + TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_2_0, "dvbt cell ids status"); + if (mDvbtCellIds == null) { + throw new IllegalStateException("dvbt cell ids are empty"); + } + return mDvbtCellIds; + } + + /** * Information of each tuning Physical Layer Pipes. */ public static class Atsc3PlpTuningInfo { diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java index cb35edb15c62..8917050fcd24 100644 --- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -94,4 +94,7 @@ public interface ScanCallback { /** DVBC Frontend Annex reported. */ default void onDvbcAnnexReported(@DvbcFrontendSettings.Annex int dvbcAnnex) {} + + /** DVBT Frontend Cell Ids reported. */ + default void onDvbtCellIdsReported(@NonNull int[] dvbtCellIds) {} } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index e91e2381bd42..712add4ffc3c 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -1191,6 +1191,14 @@ void FrontendClientCallbackImpl::executeOnScanMessage( dvbcAnnex); break; } + case FrontendScanMessageType::DVBT_CELL_IDS: { + std::vector<int32_t> jintV = message.get<FrontendScanMessage::dvbtCellIds>(); + jintArray cellIds = env->NewIntArray(jintV.size()); + env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0])); + env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"), + cellIds); + break; + } default: break; } @@ -2519,6 +2527,16 @@ jobject JTuner::getFrontendStatus(jintArray types) { env->SetObjectField(statusObj, field, valObj); break; } + case FrontendStatus::Tag::dvbtCellIds: { + jfieldID field = env->GetFieldID(clazz, "mDvbtCellIds", "[I"); + std::vector<int32_t> ids = s.get<FrontendStatus::Tag::dvbtCellIds>(); + + jintArray valObj = env->NewIntArray(v.size()); + env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0])); + + env->SetObjectField(statusObj, field, valObj); + break; + } } } return statusObj; diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml index c87bac67fdff..313e164cdbef 100644 --- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml @@ -14,6 +14,7 @@ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/activity_confirmation" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/dialog_background" @@ -23,6 +24,8 @@ android:padding="18dp" android:layout_gravity="center"> + <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. --> + <TextView android:id="@+id/title" android:layout_width="match_parent" @@ -30,7 +33,6 @@ android:gravity="center" android:paddingHorizontal="12dp" style="@*android:style/TextAppearance.Widget.Toolbar.Title"/> - <!-- style="@*android:style/TextAppearance.Widget.Toolbar.Title" --> <TextView android:id="@+id/summary" @@ -61,8 +63,10 @@ android:orientation="horizontal" android:gravity="end"> + <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> + <Button - android:id="@+id/button_cancel" + android:id="@+id/btn_negative" style="@android:style/Widget.Material.Button.Borderless.Colored" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -70,7 +74,7 @@ android:textColor="?android:attr/textColorSecondary" /> <Button - android:id="@+id/button_allow" + android:id="@+id/btn_positive" style="@android:style/Widget.Material.Button.Borderless.Colored" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml new file mode 100644 index 000000000000..d79aea6fae6b --- /dev/null +++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_item_device" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical" + android:padding="12dp"> + + <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. --> + + <ImageView + android:id="@android:id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginRight="12dp"/> + + <TextView + android:id="@android:id/text1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceListItemSmall"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index cc887c34414e..d84df51b9574 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -21,6 +21,8 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PRO import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; +import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE; +import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.TIMEOUT_OBSERVABLE; import static com.android.companiondevicemanager.Utils.getApplicationLabel; import static com.android.companiondevicemanager.Utils.getHtmlFromResources; import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc; @@ -91,10 +93,13 @@ public class CompanionDeviceActivity extends Activity { // The flag used to prevent double taps, that may lead to sending several requests for creating // an association to CDM. - private boolean mAssociationApproved; + private boolean mApproved; + private boolean mCancelled; @Override public void onCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreate()"); + super.onCreate(savedInstanceState); getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); } @@ -117,26 +122,13 @@ public class CompanionDeviceActivity extends Activity { // Start discovery services if needed. if (!mRequest.isSelfManaged()) { CompanionDeviceDiscoveryService.startForRequest(this, mRequest); + TIMEOUT_OBSERVABLE.addObserver((o, arg) -> cancel(true)); } // Init UI. initUI(); } @Override - protected void onStop() { - super.onStop(); - if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing()); - - // TODO: handle config changes without cancelling. - if (!isFinishing()) { - cancel(); // will finish() - } - - // mAdapter may be observing - need to remove it. - CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.deleteObservers(); - } - - @Override protected void onNewIntent(Intent intent) { // Handle another incoming request (while we are not done with the original - mRequest - // yet). @@ -153,6 +145,39 @@ public class CompanionDeviceActivity extends Activity { } } + @Override + protected void onStop() { + super.onStop(); + if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing()); + + // TODO: handle config changes without cancelling. + if (!isDone()) { + cancel(false); // will finish() + } + + TIMEOUT_OBSERVABLE.deleteObservers(); + // mAdapter may also be observing - need to remove it. + SCAN_RESULTS_OBSERVABLE.deleteObservers(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (DEBUG) Log.d(TAG, "onDestroy()"); + } + + @Override + public void onBackPressed() { + if (DEBUG) Log.d(TAG, "onBackPressed()"); + super.onBackPressed(); + } + + @Override + public void finish() { + if (DEBUG) Log.d(TAG, "finish()", new Exception("Stack Trace Dump")); + super.finish(); + } + private void initUI() { if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest); @@ -164,10 +189,9 @@ public class CompanionDeviceActivity extends Activity { mListView = findViewById(R.id.device_list); mListView.setOnItemClickListener((av, iv, position, id) -> onListItemClick(position)); - mButtonAllow = findViewById(R.id.button_allow); - mButtonAllow.setOnClickListener(this::onAllowButtonClick); - - findViewById(R.id.button_cancel).setOnClickListener(v -> cancel()); + mButtonAllow = findViewById(R.id.btn_positive); + mButtonAllow.setOnClickListener(this::onPositiveButtonClick); + findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick); final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName()); if (mRequest.isSelfManaged()) { @@ -179,6 +203,33 @@ public class CompanionDeviceActivity extends Activity { } } + private void onAssociationApproved(@Nullable MacAddress macAddress) { + if (isDone()) { + if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled")); + return; + } + mApproved = true; + + if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress); + + if (!mRequest.isSelfManaged()) { + requireNonNull(macAddress); + CompanionDeviceDiscoveryService.stop(this); + } + + final Bundle data = new Bundle(); + data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest); + data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder()); + if (macAddress != null) { + data.putParcelable(EXTRA_MAC_ADDRESS, macAddress); + } + + data.putParcelable(EXTRA_RESULT_RECEIVER, + prepareResultReceiverForIpc(mOnAssociationCreatedReceiver)); + + mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data); + } + private void onAssociationCreated(@NonNull AssociationInfo association) { if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association); @@ -186,17 +237,26 @@ public class CompanionDeviceActivity extends Activity { setResultAndFinish(association); } - private void cancel() { - if (DEBUG) Log.i(TAG, "cancel()"); + private void cancel(boolean discoveryTimeout) { + if (DEBUG) { + Log.i(TAG, "cancel(), discoveryTimeout=" + discoveryTimeout, + new Exception("Stack Trace Dump")); + } + + if (isDone()) { + if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled")); + return; + } + mCancelled = true; // Stop discovery service if it was used. - if (!mRequest.isSelfManaged()) { + if (!mRequest.isSelfManaged() || discoveryTimeout) { CompanionDeviceDiscoveryService.stop(this); } // First send callback to the app directly... try { - mAppCallback.onFailure("Cancelled."); + mAppCallback.onFailure(discoveryTimeout ? "Timeout." : "Cancelled."); } catch (RemoteException ignore) { } @@ -297,7 +357,7 @@ public class CompanionDeviceActivity extends Activity { mSummary.setText(summary); mAdapter = new DeviceListAdapter(this); - CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter); + SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter); // TODO: hide the list and show a spinner until a first device matching device is found. mListView.setAdapter(mAdapter); @@ -313,8 +373,8 @@ public class CompanionDeviceActivity extends Activity { onAssociationApproved(macAddress); } - private void onAllowButtonClick(View v) { - if (DEBUG) Log.d(TAG, "onAllowButtonClick()"); + private void onPositiveButtonClick(View v) { + if (DEBUG) Log.d(TAG, "on_Positive_ButtonClick()"); // Disable the button, to prevent more clicks. v.setEnabled(false); @@ -330,28 +390,17 @@ public class CompanionDeviceActivity extends Activity { onAssociationApproved(macAddress); } - private void onAssociationApproved(@Nullable MacAddress macAddress) { - if (mAssociationApproved) return; - mAssociationApproved = true; + private void onNegativeButtonClick(View v) { + if (DEBUG) Log.d(TAG, "on_Negative_ButtonClick()"); - if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress); - - if (!mRequest.isSelfManaged()) { - requireNonNull(macAddress); - CompanionDeviceDiscoveryService.stop(this); - } - - final Bundle data = new Bundle(); - data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest); - data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder()); - if (macAddress != null) { - data.putParcelable(EXTRA_MAC_ADDRESS, macAddress); - } + // Disable the button, to prevent more clicks. + v.setEnabled(false); - data.putParcelable(EXTRA_RESULT_RECEIVER, - prepareResultReceiverForIpc(mOnAssociationCreatedReceiver)); + cancel(false); + } - mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data); + private boolean isDone() { + return mApproved || mCancelled; } private final ResultReceiver mOnAssociationCreatedReceiver = diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index a4ff1dc00fda..f859130804b9 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -22,6 +22,8 @@ import static com.android.internal.util.CollectionUtils.filter; import static com.android.internal.util.CollectionUtils.find; import static com.android.internal.util.CollectionUtils.map; +import static java.lang.Math.max; +import static java.lang.Math.min; import static java.util.Objects.requireNonNull; import android.annotation.MainThread; @@ -50,6 +52,7 @@ import android.net.wifi.WifiManager; import android.os.Handler; import android.os.IBinder; import android.os.Parcelable; +import android.os.SystemProperties; import android.text.TextUtils; import android.util.Log; @@ -64,13 +67,17 @@ public class CompanionDeviceDiscoveryService extends Service { private static final boolean DEBUG = false; private static final String TAG = CompanionDeviceDiscoveryService.class.getSimpleName(); + private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout"; + private static final long TIMEOUT_DEFAULT = 20_000L; // 20 seconds + private static final long TIMEOUT_MIN = 1_000L; // 1 sec + private static final long TIMEOUT_MAX = 60_000L; // 1 min + private static final String ACTION_START_DISCOVERY = "com.android.companiondevicemanager.action.START_DISCOVERY"; private static final String ACTION_STOP_DISCOVERY = "com.android.companiondevicemanager.action.ACTION_STOP_DISCOVERY"; private static final String EXTRA_ASSOCIATION_REQUEST = "association_request"; - private static final long SCAN_TIMEOUT = 20_000L; // 20 seconds // TODO: replace with LiveData-s? static final Observable TIMEOUT_OBSERVABLE = new MyObservable(); @@ -180,8 +187,7 @@ public class CompanionDeviceDiscoveryService extends Service { // Start BLE scanning (if needed) mBleScanCallback = startBleScanningIfNeeded(bleFilters, forceStartScanningAll); - // Schedule a time-out. - Handler.getMain().postDelayed(mTimeoutRunnable, SCAN_TIMEOUT); + scheduleTimeout(); } @MainThread @@ -338,6 +344,21 @@ public class CompanionDeviceDiscoveryService extends Service { }); } + private void scheduleTimeout() { + long timeout = SystemProperties.getLong(SYS_PROP_DEBUG_TIMEOUT, -1); + if (timeout <= 0) { + // 0 or negative values indicate that the sysprop was never set or should be ignored. + timeout = TIMEOUT_DEFAULT; + } else { + timeout = min(timeout, TIMEOUT_MAX); // should be <= 1 min (TIMEOUT_MAX) + timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN) + } + + if (DEBUG) Log.d(TAG, "scheduleTimeout(), timeout=" + timeout); + + Handler.getMain().postDelayed(mTimeoutRunnable, timeout); + } + private void timeout() { if (DEBUG) Log.i(TAG, "timeout()"); stopDiscoveryAndFinish(); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java index cf2a2bfa468b..2499cf06f507 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java @@ -16,18 +16,14 @@ package com.android.companiondevicemanager; -import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.util.TypedValue; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import android.widget.ImageView; import android.widget.TextView; import java.util.List; @@ -39,51 +35,12 @@ import java.util.Observer; */ class DeviceListAdapter extends BaseAdapter implements Observer { private final Context mContext; - private final Resources mResources; - - private final Drawable mBluetoothIcon; - private final Drawable mWifiIcon; - - private final @ColorInt int mTextColor; // List if pairs (display name, address) private List<DeviceFilterPair<?>> mDevices; DeviceListAdapter(Context context) { mContext = context; - mResources = context.getResources(); - mBluetoothIcon = getTintedIcon(mResources, android.R.drawable.stat_sys_data_bluetooth); - mWifiIcon = getTintedIcon(mResources, com.android.internal.R.drawable.ic_wifi_signal_3); - mTextColor = getColor(context, android.R.attr.colorForeground); - } - - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - final TextView view = convertView != null ? (TextView) convertView : newView(); - bind(view, getItem(position)); - return view; - } - - private void bind(TextView textView, DeviceFilterPair<?> item) { - textView.setText(item.getDisplayName()); - textView.setBackgroundColor(Color.TRANSPARENT); - /* - textView.setCompoundDrawablesWithIntrinsicBounds( - item.getDevice() instanceof android.net.wifi.ScanResult - ? mWifiIcon - : mBluetoothIcon, - null, null, null); - textView.getCompoundDrawables()[0].setTint(mTextColor); - */ - } - - private TextView newView() { - final TextView textView = new TextView(mContext); - textView.setTextColor(mTextColor); - final int padding = 24; - textView.setPadding(padding, padding, padding, padding); - //textView.setCompoundDrawablePadding(padding); - return textView; } @Override @@ -107,17 +64,29 @@ class DeviceListAdapter extends BaseAdapter implements Observer { notifyDataSetChanged(); } - private @ColorInt int getColor(Context context, int attr) { - final TypedArray a = context.obtainStyledAttributes(new TypedValue().data, - new int[] { attr }); - final int color = a.getColor(0, 0); - a.recycle(); - return color; + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + final View view = convertView != null + ? convertView + : LayoutInflater.from(mContext).inflate(R.layout.list_item_device, parent, false); + + final DeviceFilterPair<?> item = getItem(position); + bindView(view, item); + + return view; } - private static Drawable getTintedIcon(Resources resources, int drawableRes) { - Drawable icon = resources.getDrawable(drawableRes, null); - icon.setTint(Color.DKGRAY); - return icon; + private void bindView(@NonNull View view, DeviceFilterPair<?> item) { + final TextView textView = view.findViewById(android.R.id.text1); + textView.setText(item.getDisplayName()); + + final ImageView iconView = view.findViewById(android.R.id.icon); + + // TODO(b/211417476): Set either Bluetooth or WiFi icon. + iconView.setVisibility(View.GONE); + // final int iconRes = isBt ? android.R.drawable.stat_sys_data_bluetooth + // : com.android.internal.R.drawable.ic_wifi_signal_3; + // final Drawable icon = getTintedIcon(mResources, iconRes); + // iconView.setImageDrawable(icon); } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java index 837629911ccd..0d15dffae92d 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java @@ -17,8 +17,6 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; -import static com.android.internal.util.Preconditions.checkNotNull; - import android.annotation.NonNull; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -46,6 +44,7 @@ import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; +import java.util.Objects; /** * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply @@ -86,6 +85,7 @@ public final class IpSecManager { * * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final int DIRECTION_FWD = 2; /** @@ -988,7 +988,7 @@ public final class IpSecManager { */ public IpSecManager(Context ctx, IIpSecService service) { mContext = ctx; - mService = checkNotNull(service, "missing service"); + mService = Objects.requireNonNull(service, "missing service"); } private static void maybeHandleServiceSpecificException(ServiceSpecificException sse) { diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java index b48c1fdaf1b2..36199a046cfc 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java @@ -33,7 +33,6 @@ import android.os.ServiceSpecificException; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; @@ -41,6 +40,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; +import java.util.Objects; /** * This class represents a transform, which roughly corresponds to an IPsec Security Association. @@ -255,7 +255,7 @@ public final class IpSecTransform implements AutoCloseable { @NonNull public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. - Preconditions.checkNotNull(algo); + Objects.requireNonNull(algo); mConfig.setEncryption(algo); return this; } @@ -270,7 +270,7 @@ public final class IpSecTransform implements AutoCloseable { @NonNull public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. - Preconditions.checkNotNull(algo); + Objects.requireNonNull(algo); mConfig.setAuthentication(algo); return this; } @@ -290,7 +290,7 @@ public final class IpSecTransform implements AutoCloseable { */ @NonNull public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { - Preconditions.checkNotNull(algo); + Objects.requireNonNull(algo); mConfig.setAuthenticatedEncryption(algo); return this; } @@ -311,7 +311,7 @@ public final class IpSecTransform implements AutoCloseable { @NonNull public IpSecTransform.Builder setIpv4Encapsulation( @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { - Preconditions.checkNotNull(localSocket); + Objects.requireNonNull(localSocket); mConfig.setEncapType(ENCAP_ESPINUDP); if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); @@ -348,8 +348,8 @@ public final class IpSecTransform implements AutoCloseable { @NonNull IpSecManager.SecurityParameterIndex spi) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { - Preconditions.checkNotNull(sourceAddress); - Preconditions.checkNotNull(spi); + Objects.requireNonNull(sourceAddress); + Objects.requireNonNull(spi); if (spi.getResourceId() == INVALID_RESOURCE_ID) { throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } @@ -387,8 +387,8 @@ public final class IpSecTransform implements AutoCloseable { @NonNull IpSecManager.SecurityParameterIndex spi) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { - Preconditions.checkNotNull(sourceAddress); - Preconditions.checkNotNull(spi); + Objects.requireNonNull(sourceAddress); + Objects.requireNonNull(spi); if (spi.getResourceId() == INVALID_RESOURCE_ID) { throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } @@ -404,7 +404,7 @@ public final class IpSecTransform implements AutoCloseable { * @param context current context */ public Builder(@NonNull Context context) { - Preconditions.checkNotNull(context); + Objects.requireNonNull(context); mContext = context; mConfig = new IpSecConfig(); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java index 3885a9e6d5cb..591605d952e9 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java @@ -24,7 +24,7 @@ import static android.net.TrafficStats.UID_TETHERING; import android.Manifest; import android.annotation.IntDef; import android.app.AppOpsManager; -import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; @@ -32,8 +32,6 @@ import android.os.Process; import android.os.UserHandle; import android.telephony.TelephonyManager; -import com.android.server.LocalServices; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -109,8 +107,7 @@ public final class NetworkStatsAccess { /** Returns the {@link NetworkStatsAccess.Level} for the given caller. */ public static @NetworkStatsAccess.Level int checkAccessLevel( Context context, int callingUid, String callingPackage) { - final DevicePolicyManagerInternal dpmi = LocalServices.getService( - DevicePolicyManagerInternal.class); + final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class); final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); boolean hasCarrierPrivileges; @@ -123,8 +120,9 @@ public final class NetworkStatsAccess { Binder.restoreCallingIdentity(token); } - final boolean isDeviceOwner = dpmi != null && dpmi.isActiveDeviceOwner(callingUid); + final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage); final int appId = UserHandle.getAppId(callingUid); + if (hasCarrierPrivileges || isDeviceOwner || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) { // Carrier-privileged apps and device owners, and the system (including the @@ -139,8 +137,8 @@ public final class NetworkStatsAccess { } //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode. - boolean isProfileOwner = dpmi != null && (dpmi.isActiveProfileOwner(callingUid) - || dpmi.isActiveDeviceOwner(callingUid)); + boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage) + || mDpm.isDeviceOwnerApp(callingPackage)); if (isProfileOwner) { // Apps with the AppOps permission, profile owners, and apps with the privileged // permission can access data usage for all apps in this user/profile. diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java index cb5a025278b1..d85291318fa4 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java @@ -47,6 +47,7 @@ import android.os.Parcelable; import android.telephony.Annotation.NetworkType; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.ArraySet; import com.android.internal.util.ArrayUtils; import com.android.net.module.util.NetworkIdentityUtils; @@ -58,6 +59,9 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; /** * Predicate used to match {@link NetworkIdentity}, usually when collecting @@ -119,20 +123,30 @@ public final class NetworkTemplate implements Parcelable { public @interface SubscriberIdMatchRule{} /** * Value of the match rule of the subscriberId to match networks with specific subscriberId. + * + * @hide */ public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0; /** * Value of the match rule of the subscriberId to match networks with any subscriberId which * includes null and non-null. + * + * @hide */ public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1; + // TODO: Remove this and replace all callers with WIFI_NETWORK_KEY_ALL. + /** @hide */ + public static final String WIFI_NETWORKID_ALL = null; + /** - * Wi-Fi Network ID is never supposed to be null (if it is, it is a bug that + * Wi-Fi Network Key is never supposed to be null (if it is, it is a bug that * should be fixed), so it's not possible to want to match null vs - * non-null. Therefore it's fine to use null as a sentinel for Network ID. + * non-null. Therefore it's fine to use null as a sentinel for Wifi Network Key. + * + * @hide */ - public static final String WIFI_NETWORKID_ALL = null; + public static final String WIFI_NETWORK_KEY_ALL = WIFI_NETWORKID_ALL; /** * Include all network types when filtering. This is meant to merge in with the @@ -278,7 +292,10 @@ public final class NetworkTemplate implements Parcelable { * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given SSID, * and IMSI. * - * Call with {@link #WIFI_NETWORKID_ALL} for {@code networkId} to get result regardless of SSID. + * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code networkId} to get result regardless + * of SSID. + * + * @hide */ public static NetworkTemplate buildTemplateWifi(@Nullable String networkId, @Nullable String subscriberId) { @@ -345,6 +362,7 @@ public final class NetworkTemplate implements Parcelable { */ private final String[] mMatchSubscriberIds; + // TODO: Change variable name to match the Api surface. private final String mNetworkId; // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. @@ -361,14 +379,14 @@ public final class NetworkTemplate implements Parcelable { // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}. private final int mOemManaged; - private void checkValidSubscriberIdMatchRule() { - switch (mMatchRule) { + private static void checkValidSubscriberIdMatchRule(int matchRule, int subscriberIdMatchRule) { + switch (matchRule) { case MATCH_MOBILE: case MATCH_CARRIER: // MOBILE and CARRIER templates must always specify a subscriber ID. - if (mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) { - throw new IllegalArgumentException("Invalid SubscriberIdMatchRule" - + "on match rule: " + getMatchRuleName(mMatchRule)); + if (subscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) { + throw new IllegalArgumentException("Invalid SubscriberIdMatchRule " + + "on match rule: " + getMatchRuleName(matchRule)); } return; default: @@ -421,7 +439,7 @@ public final class NetworkTemplate implements Parcelable { mSubType = subType; mOemManaged = oemManaged; mSubscriberIdMatchRule = subscriberIdMatchRule; - checkValidSubscriberIdMatchRule(); + checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule); if (!isKnownMatchRule(matchRule)) { throw new IllegalArgumentException("Unknown network template rule " + matchRule + " will not match any identity."); @@ -519,7 +537,7 @@ public final class NetworkTemplate implements Parcelable { return false; } - private String subscriberIdMatchRuleToString(int rule) { + private static String subscriberIdMatchRuleToString(int rule) { switch (rule) { case SUBSCRIBER_ID_MATCH_RULE_EXACT: return "EXACT_MATCH"; @@ -542,52 +560,58 @@ public final class NetworkTemplate implements Parcelable { } /** - * Check if the template can be persisted into disk. - * - * @hide + * Get match rule of the template. See {@code MATCH_*}. */ - // TODO: Move to the NetworkPolicy. - public boolean isPersistable() { + @UnsupportedAppUsage + public int getMatchRule() { + // Wildcard rules are not exposed. For external callers, convert wildcard rules to + // exposed rules before returning. switch (mMatchRule) { case MATCH_MOBILE_WILDCARD: + return MATCH_MOBILE; case MATCH_WIFI_WILDCARD: - return false; - case MATCH_CARRIER: - return mSubscriberId != null; - case MATCH_WIFI: - if (Objects.equals(mNetworkId, WIFI_NETWORKID_ALL) - && mSubscriberIdMatchRule == SUBSCRIBER_ID_MATCH_RULE_ALL) { - return false; - } - return true; + return MATCH_WIFI; default: - return true; + return mMatchRule; } } /** - * Get match rule of the template. See {@code MATCH_*}. + * Get subscriber Id of the template. */ + @Nullable @UnsupportedAppUsage - public int getMatchRule() { - return mMatchRule; + public String getSubscriberId() { + return mSubscriberId; } /** - * Get subscriber Id of the template. + * Get set of subscriber Ids of the template. + */ + @NonNull + public Set<String> getSubscriberIds() { + return new ArraySet<>(Arrays.asList(mMatchSubscriberIds)); + } + + /** + * Get Wifi Network Key of the template. See {@link WifiInfo#getCurrentNetworkKey()}. */ @Nullable - @UnsupportedAppUsage - public String getSubscriberId() { - return mSubscriberId; + public String getWifiNetworkKey() { + return mNetworkId; } + /** @hide */ + // TODO: Remove this and replace all callers with {@link #getWifiNetworkKey()}. + @Nullable public String getNetworkId() { return mNetworkId; } /** * Get Subscriber Id Match Rule of the template. + * + * @hide */ public int getSubscriberIdMatchRule() { return mSubscriberIdMatchRule; @@ -602,6 +626,38 @@ public final class NetworkTemplate implements Parcelable { } /** + * Get roaming filter of the template. + */ + @NetworkStats.Roaming + public int getRoaming() { + return mRoaming; + } + + /** + * Get the default network status filter of the template. + */ + @NetworkStats.DefaultNetwork + public int getDefaultNetworkStatus() { + return mDefaultNetwork; + } + + /** + * Get the Radio Access Technology(RAT) type filter of the template. + */ + public int getRatType() { + return mSubType; + } + + /** + * Get the OEM managed filter of the template. See {@code OEM_MANAGED_*} or + * {@code android.net.NetworkIdentity#OEM_*}. + */ + @OemManaged + public int getOemManaged() { + return mOemManaged; + } + + /** * Test if given {@link NetworkIdentity} matches this template. * * @hide @@ -680,10 +736,10 @@ public final class NetworkTemplate implements Parcelable { /** * Check if network with matching SSID. Returns true when the SSID matches, or when - * {@code mNetworkId} is {@code WIFI_NETWORKID_ALL}. + * {@code mNetworkId} is {@code WIFI_NETWORK_KEY_ALL}. */ private boolean matchesWifiNetworkId(@Nullable String networkId) { - return Objects.equals(mNetworkId, WIFI_NETWORKID_ALL) + return Objects.equals(mNetworkId, WIFI_NETWORK_KEY_ALL) || Objects.equals(sanitizeSsid(mNetworkId), sanitizeSsid(networkId)); } @@ -948,4 +1004,184 @@ public final class NetworkTemplate implements Parcelable { return new NetworkTemplate[size]; } }; + + /** + * Builder class for NetworkTemplate. + */ + public static final class Builder { + private final int mMatchRule; + // Use a SortedSet to provide a deterministic order when fetching the first one. + @NonNull + private final SortedSet<String> mMatchSubscriberIds = new TreeSet<>(); + @Nullable + private String mWifiNetworkKey; + + // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. + private int mMetered; + private int mRoaming; + private int mDefaultNetwork; + private int mRatType; + + // Bitfield containing OEM network properties {@code NetworkIdentity#OEM_*}. + private int mOemManaged; + + /** + * Creates a new Builder with given match rule to construct NetworkTemplate objects. + * + * @param matchRule the match rule of the template, see {@code MATCH_*}. + */ + public Builder(@TemplateMatchRule final int matchRule) { + assertRequestableMatchRule(matchRule); + // Initialize members with default values. + mMatchRule = matchRule; + mWifiNetworkKey = WIFI_NETWORK_KEY_ALL; + mMetered = METERED_ALL; + mRoaming = ROAMING_ALL; + mDefaultNetwork = DEFAULT_NETWORK_ALL; + mRatType = NETWORK_TYPE_ALL; + mOemManaged = OEM_MANAGED_ALL; + } + + /** + * Set the Subscriber Ids. Calling this function with an empty set represents + * the intention of matching any Subscriber Ids. + * + * @param subscriberIds the list of Subscriber Ids. + * @return this builder. + */ + @NonNull + public Builder setSubscriberIds(@NonNull Set<String> subscriberIds) { + Objects.requireNonNull(subscriberIds); + mMatchSubscriberIds.clear(); + mMatchSubscriberIds.addAll(subscriberIds); + return this; + } + + /** + * Set the Wifi Network Key. + * + * @param wifiNetworkKey the Wifi Network Key, see {@link WifiInfo#getCurrentNetworkKey()}. + * Or null to match all networks. + * @return this builder. + */ + @NonNull + public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) { + mWifiNetworkKey = wifiNetworkKey; + return this; + } + + /** + * Set the meteredness filter. + * + * @param metered the meteredness filter. + * @return this builder. + */ + @NonNull + public Builder setMeteredness(@NetworkStats.Meteredness int metered) { + mMetered = metered; + return this; + } + + /** + * Set the roaming filter. + * + * @param roaming the roaming filter. + * @return this builder. + */ + @NonNull + public Builder setRoaming(@NetworkStats.Roaming int roaming) { + mRoaming = roaming; + return this; + } + + /** + * Set the default network status filter. + * + * @param defaultNetwork the default network status filter. + * @return this builder. + */ + @NonNull + public Builder setDefaultNetworkStatus(@NetworkStats.DefaultNetwork int defaultNetwork) { + mDefaultNetwork = defaultNetwork; + return this; + } + + /** + * Set the Radio Access Technology(RAT) type filter. + * + * @param ratType the Radio Access Technology(RAT) type filter. Use + * {@link #NETWORK_TYPE_ALL} to include all network types when filtering. + * See {@code TelephonyManager.NETWORK_TYPE_*}. + * @return this builder. + */ + @NonNull + public Builder setRatType(@NetworkType int ratType) { + // Input will be validated with the match rule when building the template. + mRatType = ratType; + return this; + } + + /** + * Set the OEM managed filter. + * + * @param oemManaged the match rule to match different type of OEM managed network or + * unmanaged networks. See {@code OEM_MANAGED_*}. + * @return this builder. + */ + @NonNull + public Builder setOemManaged(@OemManaged int oemManaged) { + mOemManaged = oemManaged; + return this; + } + + /** + * Check whether the match rule is requestable. + * + * @param matchRule the target match rule to be checked. + */ + private static void assertRequestableMatchRule(final int matchRule) { + if (!isKnownMatchRule(matchRule) + || matchRule == MATCH_PROXY + || matchRule == MATCH_MOBILE_WILDCARD + || matchRule == MATCH_WIFI_WILDCARD) { + throw new IllegalArgumentException("Invalid match rule: " + + getMatchRuleName(matchRule)); + } + } + + private void assertRequestableParameters() { + // TODO: Check all the input are legitimate. + } + + /** + * For backward compatibility, deduce match rule to a wildcard match rule + * if the Subscriber Ids are empty. + */ + private int getWildcardDeducedMatchRule() { + if (mMatchRule == MATCH_MOBILE && mMatchSubscriberIds.isEmpty()) { + return MATCH_MOBILE_WILDCARD; + } else if (mMatchRule == MATCH_WIFI && mMatchSubscriberIds.isEmpty() + && mWifiNetworkKey == WIFI_NETWORK_KEY_ALL) { + return MATCH_WIFI_WILDCARD; + } + return mMatchRule; + } + + /** + * Builds the instance of the NetworkTemplate. + * + * @return the built instance of NetworkTemplate. + */ + @NonNull + public NetworkTemplate build() { + assertRequestableParameters(); + final int subscriberIdMatchRule = mMatchSubscriberIds.isEmpty() + ? SUBSCRIBER_ID_MATCH_RULE_ALL : SUBSCRIBER_ID_MATCH_RULE_EXACT; + return new NetworkTemplate(getWildcardDeducedMatchRule(), + mMatchSubscriberIds.isEmpty() ? null : mMatchSubscriberIds.iterator().next(), + mMatchSubscriberIds.toArray(new String[0]), + mWifiNetworkKey, mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, + subscriberIdMatchRule); + } + } } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 8e20e023f6e0..47b0744497ff 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1334,7 +1334,7 @@ <string name="notice_header" translatable="false"></string> <!-- Name of the phone device. [CHAR LIMIT=30] --> - <string name="media_transfer_this_device_name">Phone speaker</string> + <string name="media_transfer_this_device_name">This phone</string> <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] --> <string name="media_transfer_this_phone">This phone</string> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index cdf274f23dbb..dec3245a102f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -1348,7 +1348,6 @@ class DatabaseHelper extends SQLiteOpenHelper { Settings.Global.CONNECTIVITY_CHANGE_DELAY, Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, Settings.Global.CAPTIVE_PORTAL_SERVER, - Settings.Global.NSD_ON, Settings.Global.SET_INSTALL_LOCATION, Settings.Global.DEFAULT_INSTALL_LOCATION, Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 6072f68e3691..38a258f6ecf0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1100,10 +1100,6 @@ class SettingsProtoDumpUtil { p.end(notificationToken); dumpSetting(s, p, - Settings.Global.NSD_ON, - GlobalSettingsProto.NSD_ON); - - dumpSetting(s, p, Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE, GlobalSettingsProto.NR_NSA_TRACKING_SCREEN_OFF_MODE); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 2d1f0cb8e7c4..ed813a0b1f46 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -391,7 +391,6 @@ public class SettingsBackupTest { Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE, - Settings.Global.NSD_ON, Settings.Global.NTP_SERVER, Settings.Global.NTP_TIMEOUT, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index a3f07d8fd1b4..10252ee3bc60 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -610,6 +610,9 @@ <!-- Permission required for CTS test - CtsSafetyCenterTestCases --> <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" /> + <!-- Permission required for CTS test - CtsSafetyCenterTestCases --> + <uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" /> + <!-- Permission required for CTS test - CommunalManagerTest --> <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" /> <uses-permission android:name="android.permission.READ_COMMUNAL_STATE" /> diff --git a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml index 177f69590882..11199358b6ef 100644 --- a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml @@ -17,14 +17,14 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:paddingMode="stack" - android:paddingStart="44dp" + android:paddingStart="24dp" android:paddingEnd="44dp" android:paddingLeft="0dp" android:paddingRight="0dp"> <item> <shape android:shape="rectangle"> <solid android:color="?androidprv:attr/colorSurface" /> - <corners android:radius="@dimen/keyguard_user_switcher_corner" /> + <corners android:radius="32dp" /> </shape> </item> <item diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml new file mode 100644 index 000000000000..5bb5690b8f34 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> + +<shape android:shape="rectangle" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <solid android:color="?androidprv:attr/colorAccentPrimary" /> + <corners android:radius="24dp" /> +</shape> diff --git a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml index 96a2d1534ebe..74ece15aa78c 100644 --- a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml @@ -18,5 +18,5 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> <solid android:color="?androidprv:attr/colorSurface" /> - <corners android:radius="@dimen/keyguard_user_switcher_popup_corner" /> + <corners android:radius="28dp" /> </shape> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml index 4f0925f3bfbb..36035fc87e40 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml @@ -30,8 +30,8 @@ <ImageView android:id="@+id/user_icon" - android:layout_width="@dimen/keyguard_user_switcher_icon_size" - android:layout_height="@dimen/keyguard_user_switcher_icon_size" /> + android:layout_width="@dimen/bouncer_user_switcher_icon_size" + android:layout_height="@dimen/bouncer_user_switcher_icon_size" /> <!-- need to keep this outer view in order to have a correctly sized anchor for the dropdown menu, as well as dropdown background in the right place --> @@ -40,13 +40,12 @@ android:orientation="horizontal" android:layout_height="wrap_content" android:layout_width="wrap_content" - android:layout_marginTop="30dp" - android:minHeight="48dp"> + android:layout_marginTop="30dp"> <TextView - style="@style/Keyguard.UserSwitcher.Spinner.Header" + style="@style/Bouncer.UserSwitcher.Spinner.Header" android:clickable="false" android:id="@+id/user_switcher_header" - android:layout_width="@dimen/keyguard_user_switcher_width" + android:layout_width="@dimen/bouncer_user_switcher_width" android:layout_height="wrap_content" /> </LinearLayout>> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml index b08e1ff4c472..c388f15be6e4 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml @@ -13,13 +13,14 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<TextView +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - style="@style/Keyguard.UserSwitcher.Spinner.Item" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:paddingStart="@dimen/control_menu_horizontal_padding" - android:paddingEnd="@dimen/control_menu_horizontal_padding" - android:textDirection="locale"/> - + android:layout_height="wrap_content"> + <TextView + style="@style/Bouncer.UserSwitcher.Spinner.Item" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" /> +</FrameLayout> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 2819dc9c27b7..c8bb8e99be17 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -108,11 +108,15 @@ <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen> - <dimen name="keyguard_user_switcher_header_text_size">32sp</dimen> - <dimen name="keyguard_user_switcher_item_text_size">32sp</dimen> - <dimen name="keyguard_user_switcher_width">320dp</dimen> - <dimen name="keyguard_user_switcher_icon_size">310dp</dimen> - <dimen name="keyguard_user_switcher_corner">32dp</dimen> - <dimen name="keyguard_user_switcher_popup_corner">24dp</dimen> - <dimen name="keyguard_user_switcher_item_padding_vertical">15dp</dimen> + <dimen name="bouncer_user_switcher_header_text_size">20sp</dimen> + <dimen name="bouncer_user_switcher_item_text_size">20sp</dimen> + <dimen name="bouncer_user_switcher_item_line_height">24sp</dimen> + <dimen name="bouncer_user_switcher_item_icon_size">28dp</dimen> + <dimen name="bouncer_user_switcher_item_icon_padding">12dp</dimen> + <dimen name="bouncer_user_switcher_width">248dp</dimen> + <dimen name="bouncer_user_switcher_icon_size">190dp</dimen> + <dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen> + <dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen> + <dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen> + <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 60f034ae7ee8..5048f856ac70 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -141,22 +141,23 @@ <item name="android:shadowRadius">0</item> </style> - <style name="Keyguard.UserSwitcher.Spinner" parent="@android:style/Widget.DeviceDefault.TextView"> + <style name="Bouncer.UserSwitcher.Spinner" parent="@android:style/Widget.DeviceDefault.TextView"> <item name="android:textColor">?android:attr/textColorPrimary</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> <item name="android:singleLine">true</item> <item name="android:ellipsize">end</item> - <item name="android:paddingTop">@dimen/keyguard_user_switcher_item_padding_vertical</item> - <item name="android:paddingBottom">@dimen/keyguard_user_switcher_item_padding_vertical</item> + <item name="android:minHeight">48dp</item> + <item name="android:paddingVertical">@dimen/bouncer_user_switcher_item_padding_vertical</item> + <item name="android:paddingHorizontal">@dimen/bouncer_user_switcher_item_padding_horizontal</item> + <item name="android:lineHeight">@dimen/bouncer_user_switcher_item_line_height</item> + <item name="android:gravity">start|center_vertical</item> </style> - <style name="Keyguard.UserSwitcher.Spinner.Header"> - <item name="android:background">@drawable/keyguard_user_switcher_header_bg</item> - <item name="android:textSize">@dimen/keyguard_user_switcher_header_text_size</item> + <style name="Bouncer.UserSwitcher.Spinner.Header"> + <item name="android:background">@drawable/bouncer_user_switcher_header_bg</item> + <item name="android:textSize">@dimen/bouncer_user_switcher_header_text_size</item> </style> - <style name="Keyguard.UserSwitcher.Spinner.Item"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textSize">@dimen/keyguard_user_switcher_item_text_size</item> + <style name="Bouncer.UserSwitcher.Spinner.Item"> + <item name="android:textSize">@dimen/bouncer_user_switcher_item_text_size</item> </style> </resources> diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml index 6fbc41c7e20d..2d082dc7d5e2 100644 --- a/packages/SystemUI/res/layout/media_ttt_chip.xml +++ b/packages/SystemUI/res/layout/media_ttt_chip.xml @@ -26,6 +26,13 @@ android:gravity="center_vertical" > + <com.android.internal.widget.CachingIconView + android:id="@+id/app_icon" + android:layout_width="@dimen/media_ttt_icon_size" + android:layout_height="@dimen/media_ttt_icon_size" + android:layout_marginEnd="12dp" + /> + <TextView android:id="@+id/text" android:layout_width="wrap_content" @@ -37,8 +44,8 @@ <ProgressBar android:id="@+id/loading" android:indeterminate="true" - android:layout_width="@dimen/media_ttt_icon_size" - android:layout_height="@dimen/media_ttt_icon_size" + android:layout_width="@dimen/media_ttt_loading_size" + android:layout_height="@dimen/media_ttt_loading_size" android:layout_marginStart="12dp" android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant" style="?android:attr/progressBarStyleSmall" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 843c69fb1f49..b7f25e81046c 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -980,7 +980,8 @@ <!-- Media tap-to-transfer chip --> <dimen name="media_ttt_chip_outer_padding">16dp</dimen> <dimen name="media_ttt_text_size">16sp</dimen> - <dimen name="media_ttt_icon_size">16dp</dimen> + <dimen name="media_ttt_icon_size">24dp</dimen> + <dimen name="media_ttt_loading_size">20dp</dimen> <dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen> <dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index b84cb19b9468..b5ea498f185d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -32,9 +32,14 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BlendMode; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.os.UserManager; import android.provider.Settings; import android.util.AttributeSet; import android.util.Log; @@ -70,6 +75,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.UserIcons; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.settingslib.Utils; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -760,11 +766,11 @@ public class KeyguardSecurityContainer extends FrameLayout { private ViewGroup mView; private ViewGroup mUserSwitcherViewGroup; private KeyguardSecurityViewFlipper mViewFlipper; - private ImageView mUserIconView; private TextView mUserSwitcher; private FalsingManager mFalsingManager; private UserSwitcherController mUserSwitcherController; private KeyguardUserSwitcherPopupMenu mPopup; + private Resources mResources; @Override public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, @@ -775,6 +781,7 @@ public class KeyguardSecurityContainer extends FrameLayout { mViewFlipper = viewFlipper; mFalsingManager = falsingManager; mUserSwitcherController = userSwitcherController; + mResources = v.getContext().getResources(); if (mUserSwitcherViewGroup == null) { LayoutInflater.from(v.getContext()).inflate( @@ -784,9 +791,8 @@ public class KeyguardSecurityContainer extends FrameLayout { mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher); } - mUserIconView = mView.findViewById(R.id.user_icon); - Drawable icon = UserIcons.getDefaultUserIcon(v.getContext().getResources(), 0, false); - mUserIconView.setImageDrawable(icon); + Drawable userIcon = findUserIcon(KeyguardUpdateMonitor.getCurrentUser()); + ((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon); updateSecurityViewLocation(); @@ -802,6 +808,14 @@ public class KeyguardSecurityContainer extends FrameLayout { } } + private Drawable findUserIcon(int userId) { + Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId); + if (userIcon != null) { + return new BitmapDrawable(userIcon); + } + return UserIcons.getDefaultUserIcon(mResources, userId, false); + } + @Override public void startAppearAnimation(SecurityMode securityMode) { // IME insets animations handle alpha and translation @@ -824,8 +838,7 @@ public class KeyguardSecurityContainer extends FrameLayout { return; } - int yTranslation = mView.getContext().getResources().getDimensionPixelSize( - R.dimen.disappear_y_translation); + int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); AnimatorSet anims = new AnimatorSet(); ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); @@ -840,21 +853,70 @@ public class KeyguardSecurityContainer extends FrameLayout { String currentUserName = mUserSwitcherController.getCurrentUserName(); mUserSwitcher.setText(currentUserName); + final UserRecord currentUser = getCurrentUser(); ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor); BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { UserRecord item = getItem(position); - TextView view = (TextView) convertView; + FrameLayout view = (FrameLayout) convertView; if (view == null) { - view = (TextView) LayoutInflater.from(parent.getContext()).inflate( + view = (FrameLayout) LayoutInflater.from(parent.getContext()).inflate( R.layout.keyguard_bouncer_user_switcher_item, parent, false); } - view.setText(getName(parent.getContext(), item)); + TextView textView = (TextView) view.getChildAt(0); + textView.setText(getName(parent.getContext(), item)); + Drawable icon = null; + if (item.picture != null) { + icon = new BitmapDrawable(item.picture); + } else { + icon = getDrawable(item, view.getContext()); + } + int iconSize = view.getResources().getDimensionPixelSize( + R.dimen.bouncer_user_switcher_item_icon_size); + int iconPadding = view.getResources().getDimensionPixelSize( + R.dimen.bouncer_user_switcher_item_icon_padding); + icon.setBounds(0, 0, iconSize, iconSize); + textView.setCompoundDrawablePadding(iconPadding); + textView.setCompoundDrawablesRelative(icon, null, null, null); + + if (item == currentUser) { + textView.setBackground(view.getContext().getDrawable( + R.drawable.bouncer_user_switcher_item_selected_bg)); + } else { + textView.setBackground(null); + } return view; } + + private Drawable getDrawable(UserRecord item, Context context) { + Drawable drawable; + if (item.isCurrent && item.isGuest) { + drawable = context.getDrawable(R.drawable.ic_avatar_guest_user); + } else { + drawable = getIconDrawable(context, item); + } + + int iconColor; + if (item.isSwitchToEnabled) { + iconColor = Utils.getColorAttrDefaultColor(context, + com.android.internal.R.attr.colorAccentPrimaryVariant); + } else { + iconColor = context.getResources().getColor( + R.color.kg_user_switcher_restricted_avatar_icon_color, + context.getTheme()); + } + drawable.setTint(iconColor); + + Drawable bg = context.getDrawable(R.drawable.kg_bg_avatar); + bg.setTintBlendMode(BlendMode.DST); + bg.setTint(Utils.getColorAttrDefaultColor(context, + com.android.internal.R.attr.colorSurfaceVariant)); + drawable = new LayerDrawable(new Drawable[]{bg, drawable}); + return drawable; + } }; if (adapter.getCount() < 2) { @@ -876,7 +938,8 @@ public class KeyguardSecurityContainer extends FrameLayout { public void onItemClick(AdapterView parent, View view, int pos, long id) { if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; - UserRecord user = adapter.getItem(pos); + // Subtract one for the header + UserRecord user = adapter.getItem(pos - 1); if (!user.isCurrent) { adapter.onUserListItemClicked(user); } @@ -888,6 +951,16 @@ public class KeyguardSecurityContainer extends FrameLayout { }); } + private UserRecord getCurrentUser() { + for (int i = 0; i < mUserSwitcherController.getUsers().size(); ++i) { + UserRecord userRecord = mUserSwitcherController.getUsers().get(i); + if (userRecord.isCurrent) { + return userRecord; + } + } + return null; + } + /** * Each view will get half the width. Yes, it would be easier to use something other than * FrameLayout but it was too disruptive to downstream projects to change. @@ -901,8 +974,7 @@ public class KeyguardSecurityContainer extends FrameLayout { @Override public void updateSecurityViewLocation() { - if (mView.getContext().getResources().getConfiguration().orientation - == Configuration.ORIENTATION_PORTRAIT) { + if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL); updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL); mUserSwitcherViewGroup.setTranslationY(0); @@ -912,8 +984,7 @@ public class KeyguardSecurityContainer extends FrameLayout { // Attempt to reposition a bit higher to make up for this frame being a bit lower // on the device - int yTrans = mView.getContext().getResources().getDimensionPixelSize( - R.dimen.status_bar_height); + int yTrans = mResources.getDimensionPixelSize(R.dimen.status_bar_height); mUserSwitcherViewGroup.setTranslationY(-yTrans); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java index 7b6ce3e1c951..efa5558f5088 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java @@ -18,6 +18,8 @@ package com.android.keyguard; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.drawable.ShapeDrawable; import android.view.MotionEvent; import android.view.View; import android.widget.ListPopupWindow; @@ -32,15 +34,6 @@ import com.android.systemui.plugins.FalsingManager; public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow { private Context mContext; private FalsingManager mFalsingManager; - private int mLastHeight = -1; - private View.OnLayoutChangeListener mLayoutListener = (v, l, t, r, b, ol, ot, or, ob) -> { - int height = -v.getMeasuredHeight() + getAnchorView().getHeight(); - if (height != mLastHeight) { - mLastHeight = height; - setVerticalOffset(height); - KeyguardUserSwitcherPopupMenu.super.show(); - } - }; public KeyguardUserSwitcherPopupMenu(@NonNull Context context, @NonNull FalsingManager falsingManager) { @@ -49,7 +42,7 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow { mFalsingManager = falsingManager; Resources res = mContext.getResources(); setBackgroundDrawable( - res.getDrawable(R.drawable.keyguard_user_switcher_popup_bg, context.getTheme())); + res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())); setModal(true); setOverlapAnchor(true); } @@ -63,8 +56,20 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow { super.show(); ListView listView = getListView(); - // This will force the popupwindow to show upward instead of drop down - listView.addOnLayoutChangeListener(mLayoutListener); + listView.setVerticalScrollBarEnabled(false); + listView.setHorizontalScrollBarEnabled(false); + + // Creates a transparent spacer between items + ShapeDrawable shape = new ShapeDrawable(); + shape.setAlpha(0); + listView.setDivider(shape); + listView.setDividerHeight(mContext.getResources().getDimensionPixelSize( + R.dimen.bouncer_user_switcher_popup_divider_height)); + + int height = mContext.getResources().getDimensionPixelSize( + R.dimen.bouncer_user_switcher_popup_header_height); + listView.addHeaderView(createSpacer(height), null, false); + listView.addFooterView(createSpacer(height), null, false); listView.setOnTouchListener((v, ev) -> { if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { @@ -72,11 +77,19 @@ public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow { } return false; }); + super.show(); } - @Override - public void dismiss() { - getListView().removeOnLayoutChangeListener(mLayoutListener); - super.dismiss(); + private View createSpacer(int height) { + return new View(mContext) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(1, height); + } + + @Override + public void draw(Canvas canvas) { + } + }; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 90a1e5e64daf..f82ea790bb64 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -80,11 +80,6 @@ class AuthRippleController @Inject constructor( private var circleReveal: LightRevealEffect? = null private var udfpsController: UdfpsController? = null - - private var dwellScale = 2f - private var expandedDwellScale = 2.5f - private var aodDwellScale = 1.9f - private var aodExpandedDwellScale = 2.3f private var udfpsRadius: Float = -1f override fun onInit() { @@ -128,7 +123,7 @@ class AuthRippleController @Inject constructor( updateSensorLocation() if (biometricSourceType == BiometricSourceType.FINGERPRINT && fingerprintSensorLocation != null) { - mView.setSensorLocation(fingerprintSensorLocation!!) + mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius) showUnlockedRipple() } else if (biometricSourceType == BiometricSourceType.FACE && faceSensorLocation != null) { @@ -241,24 +236,12 @@ class AuthRippleController @Inject constructor( } private fun updateRippleColor() { - mView.setColor( - Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) + mView.setLockScreenColor(Utils.getColorAttrDefaultColor(sysuiContext, + R.attr.wallpaperTextColorAccent)) } private fun showDwellRipple() { - if (statusBarStateController.isDozing) { - mView.startDwellRipple( - /* startRadius */ udfpsRadius, - /* endRadius */ udfpsRadius * aodDwellScale, - /* expandedRadius */ udfpsRadius * aodExpandedDwellScale, - /* isDozing */ true) - } else { - mView.startDwellRipple( - /* startRadius */ udfpsRadius, - /* endRadius */ udfpsRadius * dwellScale, - /* expandedRadius */ udfpsRadius * expandedDwellScale, - /* isDozing */ false) - } + mView.startDwellRipple(statusBarStateController.isDozing) } private val keyguardUpdateMonitorCallback = @@ -295,7 +278,7 @@ class AuthRippleController @Inject constructor( return } - mView.setSensorLocation(fingerprintSensorLocation!!) + mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius) showDwellRipple() } @@ -307,8 +290,8 @@ class AuthRippleController @Inject constructor( private val authControllerCallback = object : AuthController.Callback { override fun onAllAuthenticatorsRegistered() { - updateSensorLocation() updateUdfpsDependentParams() + updateSensorLocation() } override fun onEnrollmentsChanged() { @@ -329,20 +312,6 @@ class AuthRippleController @Inject constructor( } inner class AuthRippleCommand : Command { - fun printLockScreenDwellInfo(pw: PrintWriter) { - pw.println("lock screen dwell ripple: " + - "\n\tsensorLocation=$fingerprintSensorLocation" + - "\n\tdwellScale=$dwellScale" + - "\n\tdwellExpand=$expandedDwellScale") - } - - fun printAodDwellInfo(pw: PrintWriter) { - pw.println("aod dwell ripple: " + - "\n\tsensorLocation=$fingerprintSensorLocation" + - "\n\tdwellScale=$aodDwellScale" + - "\n\tdwellExpand=$aodExpandedDwellScale") - } - override fun execute(pw: PrintWriter, args: List<String>) { if (args.isEmpty()) { invalidCommand(pw) @@ -350,11 +319,9 @@ class AuthRippleController @Inject constructor( when (args[0]) { "dwell" -> { showDwellRipple() - if (statusBarStateController.isDozing) { - printAodDwellInfo(pw) - } else { - printLockScreenDwellInfo(pw) - } + pw.println("lock screen dwell ripple: " + + "\n\tsensorLocation=$fingerprintSensorLocation" + + "\n\tudfpsRadius=$udfpsRadius") } "fingerprint" -> { updateSensorLocation() diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index c6d26ffb9957..d67363079e17 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorSet import android.animation.ValueAnimator import android.content.Context import android.graphics.Canvas +import android.graphics.Color import android.graphics.Paint import android.graphics.PointF import android.util.AttributeSet @@ -28,6 +29,7 @@ import android.view.View import android.view.animation.PathInterpolator import com.android.internal.graphics.ColorUtils import com.android.systemui.animation.Interpolators +import com.android.systemui.statusbar.charging.DwellRippleShader import com.android.systemui.statusbar.charging.RippleShader private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f @@ -43,23 +45,32 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f) - private val dwellPulseDuration = 50L - private val dwellAlphaDuration = dwellPulseDuration - private val dwellAlpha: Float = 1f - private val dwellExpandDuration = 1200L - dwellPulseDuration + private val dwellPulseDuration = 100L + private val dwellExpandDuration = 2000L - dwellPulseDuration - private val aodDwellPulseDuration = 50L - private var aodDwellAlphaDuration = aodDwellPulseDuration - private var aodDwellAlpha: Float = .8f - private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration + private var drawDwell: Boolean = false + private var drawRipple: Boolean = false + private var lockScreenColorVal = Color.WHITE private val retractDuration = 400L private var alphaInDuration: Long = 0 private var unlockedRippleInProgress: Boolean = false + private val dwellShader = DwellRippleShader() + private val dwellPaint = Paint() private val rippleShader = RippleShader() private val ripplePaint = Paint() private var retractAnimator: Animator? = null private var dwellPulseOutAnimator: Animator? = null + private var dwellRadius: Float = 0f + set(value) { + dwellShader.maxRadius = value + field = value + } + private var dwellOrigin: PointF = PointF() + set(value) { + dwellShader.origin = value + field = value + } private var radius: Float = 0f set(value) { rippleShader.radius = value @@ -76,6 +87,11 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at rippleShader.progress = 0f rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH ripplePaint.shader = rippleShader + + dwellShader.color = 0xffffffff.toInt() // default color + dwellShader.progress = 0f + dwellShader.distortionStrength = .4f + dwellPaint.shader = dwellShader visibility = GONE } @@ -84,6 +100,13 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat() } + fun setFingerprintSensorLocation(location: PointF, sensorRadius: Float) { + origin = location + radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat() + dwellOrigin = location + dwellRadius = sensorRadius * 1.5f + } + fun setAlphaInDuration(duration: Long) { alphaInDuration = duration } @@ -97,14 +120,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } if (dwellPulseOutAnimator?.isRunning == true) { - val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f) + val retractRippleAnimator = ValueAnimator.ofFloat(dwellShader.progress, 0f) .apply { interpolator = retractInterpolator duration = retractDuration addUpdateListener { animator -> val now = animator.currentPlayTime - rippleShader.progress = animator.animatedValue as Float - rippleShader.time = now.toFloat() + dwellShader.progress = animator.animatedValue as Float + dwellShader.time = now.toFloat() invalidate() } @@ -114,8 +137,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at interpolator = Interpolators.LINEAR duration = retractDuration addUpdateListener { animator -> - rippleShader.color = ColorUtils.setAlphaComponent( - rippleShader.color, + dwellShader.color = ColorUtils.setAlphaComponent( + dwellShader.color, animator.animatedValue as Int ) invalidate() @@ -127,13 +150,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { dwellPulseOutAnimator?.cancel() - rippleShader.shouldFadeOutRipple = false - visibility = VISIBLE + drawDwell = true } override fun onAnimationEnd(animation: Animator?) { - visibility = GONE - resetRippleAlpha() + drawDwell = false + resetDwellAlpha() } }) start() @@ -142,101 +164,54 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } /** - * Ripple that moves animates from an outer ripple ring of - * startRadius => endRadius => expandedRadius + * Plays a ripple animation that grows to the dwellRadius with distortion. */ - fun startDwellRipple( - startRadius: Float, - endRadius: Float, - expandedRadius: Float, - isDozing: Boolean - ) { + fun startDwellRipple(isDozing: Boolean) { if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) { return } - // we divide by 4 because the desired startRadius and endRadius is for the ripple's outer - // ring see RippleShader - val startDwellProgress = startRadius / radius / 4f - val endInitialDwellProgress = endRadius / radius / 4f - val endExpandDwellProgress = expandedRadius / radius / 4f - - val alpha = if (isDozing) aodDwellAlpha else dwellAlpha - val pulseOutEndAlpha = (255 * alpha).toInt() - val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255) - val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress, - endInitialDwellProgress).apply { - interpolator = Interpolators.LINEAR_OUT_SLOW_IN - duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration - addUpdateListener { animator -> - val now = animator.currentPlayTime - rippleShader.progress = animator.animatedValue as Float - rippleShader.time = now.toFloat() - - invalidate() - } - } + updateDwellRippleColor(isDozing) - val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply { + val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(0f, .8f).apply { interpolator = Interpolators.LINEAR - duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration + duration = dwellPulseDuration addUpdateListener { animator -> - rippleShader.color = ColorUtils.setAlphaComponent( - rippleShader.color, - animator.animatedValue as Int - ) + val now = animator.currentPlayTime + dwellShader.progress = animator.animatedValue as Float + dwellShader.time = now.toFloat() + invalidate() } } // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple - val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress, - endExpandDwellProgress).apply { + val expandDwellRippleAnimator = ValueAnimator.ofFloat(.8f, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN - duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration + duration = dwellExpandDuration addUpdateListener { animator -> val now = animator.currentPlayTime - rippleShader.progress = animator.animatedValue as Float - rippleShader.time = now.toFloat() + dwellShader.progress = animator.animatedValue as Float + dwellShader.time = now.toFloat() invalidate() } } - val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha) - .apply { - interpolator = Interpolators.LINEAR - duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration - addUpdateListener { animator -> - rippleShader.color = ColorUtils.setAlphaComponent( - rippleShader.color, - animator.animatedValue as Int - ) - invalidate() - } - } - - val initialDwellPulseOutAnimator = AnimatorSet().apply { - playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator) - } - val expandDwellAnimator = AnimatorSet().apply { - playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator) - } - dwellPulseOutAnimator = AnimatorSet().apply { playSequentially( - initialDwellPulseOutAnimator, - expandDwellAnimator + dwellPulseOutRippleAnimator, + expandDwellRippleAnimator ) addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { retractAnimator?.cancel() - rippleShader.shouldFadeOutRipple = false visibility = VISIBLE + drawDwell = true } override fun onAnimationEnd(animation: Animator?) { - visibility = GONE + drawDwell = false resetRippleAlpha() } }) @@ -252,16 +227,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at return // Ignore if ripple effect is already playing } - var rippleStart = 0f - var alphaDuration = alphaInDuration - if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) { - rippleStart = rippleShader.progress - alphaDuration = 0 - dwellPulseOutAnimator?.cancel() - retractAnimator?.cancel() - } - - val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply { + val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN duration = AuthRippleController.RIPPLE_ANIMATION_DURATION addUpdateListener { animator -> @@ -274,7 +240,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply { - duration = alphaDuration + duration = alphaInDuration addUpdateListener { animator -> rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color, @@ -293,12 +259,14 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onAnimationStart(animation: Animator?) { unlockedRippleInProgress = true rippleShader.shouldFadeOutRipple = true + drawRipple = true visibility = VISIBLE } override fun onAnimationEnd(animation: Animator?) { onAnimationEnd?.run() unlockedRippleInProgress = false + drawRipple = false visibility = GONE } }) @@ -313,17 +281,42 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at ) } - fun setColor(color: Int) { - rippleShader.color = color + fun setLockScreenColor(color: Int) { + lockScreenColorVal = color + rippleShader.color = lockScreenColorVal resetRippleAlpha() } + fun updateDwellRippleColor(isDozing: Boolean) { + if (isDozing) { + dwellShader.color = Color.WHITE + } else { + dwellShader.color = lockScreenColorVal + } + resetDwellAlpha() + } + + fun resetDwellAlpha() { + dwellShader.color = ColorUtils.setAlphaComponent( + dwellShader.color, + 255 + ) + } + override fun onDraw(canvas: Canvas?) { // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover // the active effect area. Values here should be kept in sync with the // animation implementation in the ripple shader. - val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * radius * 2f - canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) + if (drawDwell) { + val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) * + (1 - dwellShader.progress)) * dwellRadius * 2f + canvas?.drawCircle(dwellOrigin.x, dwellOrigin.y, maskRadius, dwellPaint) + } + + if (drawRipple) { + val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * + (1 - rippleShader.progress)) * radius * 2f + canvas?.drawCircle(origin.x, origin.y, mask, ripplePaint) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index 9c25b3596be6..2beed4c6a7e7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -40,7 +40,6 @@ public interface DozeHost { void extendPulse(int reason); void setAnimateWakeup(boolean animateWakeup); - void setAnimateScreenOff(boolean animateScreenOff); /** * Reports that a tap event happend on the Sensors Low Power Island. diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index b2fe3bb94dd3..e568b8282856 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -21,30 +21,21 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import android.app.AlarmManager; import android.content.Context; -import android.content.res.Configuration; import android.os.Handler; import android.os.SystemClock; -import android.provider.Settings; import android.text.format.Formatter; import android.util.Log; -import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.unfold.FoldAodAnimationController; -import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus; -import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.WakeLock; import java.util.Calendar; -import java.util.Optional; import javax.inject.Inject; @@ -52,9 +43,7 @@ import javax.inject.Inject; * The policy controlling doze. */ @DozeScope -public class DozeUi implements DozeMachine.Part, TunerService.Tunable, - ConfigurationController.ConfigurationListener, FoldAodAnimationStatus, - StatusBarStateController.StateListener { +public class DozeUi implements DozeMachine.Part { // if enabled, calls dozeTimeTick() whenever the time changes: private static final boolean BURN_IN_TESTING_ENABLED = false; private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min @@ -62,26 +51,15 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, private final DozeHost mHost; private final Handler mHandler; private final WakeLock mWakeLock; - private final FoldAodAnimationController mFoldAodAnimationController; private DozeMachine mMachine; private final AlarmTimeout mTimeTicker; private final boolean mCanAnimateTransition; private final DozeParameters mDozeParameters; private final DozeLog mDozeLog; private final StatusBarStateController mStatusBarStateController; - private final TunerService mTunerService; - private final ConfigurationController mConfigurationController; - - private boolean mKeyguardShowing; private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onKeyguardVisibilityChanged(boolean showing) { - mKeyguardShowing = showing; - updateAnimateScreenOff(); - } - - @Override public void onTimeChanged() { if (BURN_IN_TESTING_ENABLED && mStatusBarStateController.isDozing()) { // update whenever the time changes for manual burn in testing @@ -91,11 +69,6 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, mHandler.post(mWakeLock.wrap(() -> {})); } } - - @Override - public void onShadeExpandedChanged(boolean expanded) { - updateAnimateScreenOff(); - } }; private long mLastTimeTickElapsed = 0; @@ -104,10 +77,8 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, public DozeUi(Context context, AlarmManager alarmManager, WakeLock wakeLock, DozeHost host, @Main Handler handler, DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor, - DozeLog dozeLog, TunerService tunerService, StatusBarStateController statusBarStateController, - Optional<SysUIUnfoldComponent> sysUiUnfoldComponent, - ConfigurationController configurationController) { + DozeLog dozeLog) { mContext = context; mWakeLock = wakeLock; mHost = host; @@ -117,31 +88,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler); keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); mDozeLog = dozeLog; - mTunerService = tunerService; mStatusBarStateController = statusBarStateController; - mStatusBarStateController.addCallback(this); - - mTunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON); - - mConfigurationController = configurationController; - mConfigurationController.addCallback(this); - - mFoldAodAnimationController = sysUiUnfoldComponent - .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); - - if (mFoldAodAnimationController != null) { - mFoldAodAnimationController.addCallback(this); - } - } - - @Override - public void destroy() { - mTunerService.removeTunable(this); - mConfigurationController.removeCallback(this); - - if (mFoldAodAnimationController != null) { - mFoldAodAnimationController.removeCallback(this); - } } @Override @@ -149,22 +96,6 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, mMachine = dozeMachine; } - /** - * Decide if we're taking over the screen-off animation - * when the device was configured to skip doze after screen off. - */ - private void updateAnimateScreenOff() { - if (mCanAnimateTransition) { - final boolean controlScreenOff = - mDozeParameters.getAlwaysOn() - && (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff()) - && !mHost.isPowerSaveActive(); - mDozeParameters.setControlScreenOffAnimation(controlScreenOff); - mHost.setAnimateScreenOff(controlScreenOff - && mDozeParameters.shouldAnimateDozingChange()); - } - } - private void pulseWhileDozing(int reason) { mHost.pulseWhileDozing( new DozeHost.PulseCallback() { @@ -293,34 +224,4 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, scheduleTimeTick(); } - - @VisibleForTesting - KeyguardUpdateMonitorCallback getKeyguardCallback() { - return mKeyguardVisibilityCallback; - } - - @Override - public void onTuningChanged(String key, String newValue) { - if (key.equals(Settings.Secure.DOZE_ALWAYS_ON)) { - updateAnimateScreenOff(); - } - } - - @Override - public void onConfigChanged(Configuration newConfig) { - updateAnimateScreenOff(); - } - - /** - * Called when StatusBar state changed, could affect unlocked screen off animation state - */ - @Override - public void onStatePostChange() { - updateAnimateScreenOff(); - } - - @Override - public void onFoldToAodAnimationChanged() { - updateAnimateScreenOff(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 1174fa8e4061..a398a7fa4575 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -99,12 +99,13 @@ public interface MediaModule { static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper( MediaTttFlags mediaTttFlags, CommandRegistry commandRegistry, + Context context, MediaTttChipController mediaTttChipController, @Main DelayableExecutor mainExecutor) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } return Optional.of(new MediaTttCommandLineHelper( - commandRegistry, mediaTttChipController, mainExecutor)); + commandRegistry, context, mediaTttChipController, mainExecutor)); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt index baa469d43e22..2b55d634b382 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt @@ -25,6 +25,7 @@ import android.view.View import android.view.WindowManager import android.widget.LinearLayout import android.widget.TextView +import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -74,6 +75,11 @@ class MediaTttChipController @Inject constructor( } val currentChipView = chipView!! + // App icon + currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply { + this.setImageDrawable(chipState.appIconDrawable) + } + // Text currentChipView.requireViewById<TextView>(R.id.text).apply { text = context.getString(chipState.chipText, chipState.otherDeviceName) @@ -128,7 +134,11 @@ class MediaTttChipController @Inject constructor( val undoRunnable = chipState.future.get(TRANSFER_TIMEOUT_SECONDS, TimeUnit.SECONDS) // Make UI changes on the main thread mainExecutor.execute { - displayChip(TransferSucceeded(chipState.otherDeviceName, undoRunnable)) + displayChip( + TransferSucceeded( + chipState.otherDeviceName, chipState.appIconDrawable, undoRunnable + ) + ) } } catch (ex: Exception) { // TODO(b/203800327): Maybe show a failure chip here if UX decides we need one. diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt index 1f308a9a6cd9..62e3b1ac8d5c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt @@ -16,6 +16,7 @@ package com.android.systemui.media.taptotransfer +import android.graphics.drawable.Drawable import androidx.annotation.StringRes import com.android.systemui.R import java.util.concurrent.Future @@ -26,12 +27,15 @@ import java.util.concurrent.Future * * This is a sealed class where each subclass represents a specific chip state. Each subclass can * contain additional information that is necessary for only that state. + * + * @property chipText a string resource for the text that the chip should display. + * @property otherDeviceName the name of the other device involved in the transfer. + * @property appIconDrawable a drawable representing the icon of the app playing the media. */ sealed class MediaTttChipState( - /** A string resource for the text that the chip should display. */ @StringRes internal val chipText: Int, - /** The name of the other device involved in the transfer. */ - internal val otherDeviceName: String + internal val otherDeviceName: String, + internal val appIconDrawable: Drawable, ) /** @@ -39,8 +43,9 @@ sealed class MediaTttChipState( * The chip will instruct the user to move closer in order to initiate the transfer. */ class MoveCloserToTransfer( - otherDeviceName: String -) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName) + otherDeviceName: String, + appIconDrawable: Drawable +) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName, appIconDrawable) /** * A state representing that a transfer has been initiated (but not completed). @@ -52,8 +57,9 @@ class MoveCloserToTransfer( */ class TransferInitiated( otherDeviceName: String, + appIconDrawable: Drawable, val future: Future<Runnable?> -) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName) +) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable) /** * A state representing that a transfer has been successfully completed. @@ -63,5 +69,6 @@ class TransferInitiated( */ class TransferSucceeded( otherDeviceName: String, + appIconDrawable: Drawable, val undoRunnable: Runnable? = null -) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName) +) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName, appIconDrawable) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 663037cc850f..74983e5bfe03 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -16,8 +16,11 @@ package com.android.systemui.media.taptotransfer +import android.content.Context +import android.graphics.drawable.Icon import android.util.Log import androidx.annotation.VisibleForTesting +import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.commandline.Command @@ -34,9 +37,13 @@ import javax.inject.Inject @SysUISingleton class MediaTttCommandLineHelper @Inject constructor( commandRegistry: CommandRegistry, + context: Context, private val mediaTttChipController: MediaTttChipController, @Main private val mainExecutor: DelayableExecutor, ) { + private val appIconDrawable = + Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) + init { commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() } commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() } @@ -47,19 +54,21 @@ class MediaTttCommandLineHelper @Inject constructor( val otherDeviceName = args[0] when (args[1]) { MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> { - mediaTttChipController.displayChip(MoveCloserToTransfer(otherDeviceName)) + mediaTttChipController.displayChip( + MoveCloserToTransfer(otherDeviceName, appIconDrawable) + ) } TRANSFER_INITIATED_COMMAND_NAME -> { val futureTask = FutureTask { fakeUndoRunnable } mediaTttChipController.displayChip( - TransferInitiated(otherDeviceName, futureTask) + TransferInitiated(otherDeviceName, appIconDrawable, futureTask) ) mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME) } TRANSFER_SUCCEEDED_COMMAND_NAME -> { mediaTttChipController.displayChip( - TransferSucceeded(otherDeviceName, fakeUndoRunnable) + TransferSucceeded(otherDeviceName, appIconDrawable, fakeUndoRunnable) ) } else -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 03d8e7e03c0f..c8115e2c197a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -92,6 +92,8 @@ class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffe override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { val interpolatedAmount = INTERPOLATOR.getInterpolation(amount) + scrim.interpolatedRevealAmount = interpolatedAmount + scrim.startColorAlpha = getPercentPastThreshold(1 - interpolatedAmount, threshold = 1 - START_COLOR_REVEAL_PERCENTAGE) @@ -152,6 +154,7 @@ class CircleReveal( // non-interpolated amount val fadeAmount = getPercentPastThreshold(amount, 0.5f) val radius = startRadius + ((endRadius - startRadius) * amount) + scrim.interpolatedRevealAmount = amount scrim.revealGradientEndColorAlpha = 1f - fadeAmount scrim.setRevealGradientBounds( centerX - radius /* left */, @@ -182,6 +185,7 @@ class PowerButtonReveal( with(scrim) { revealGradientEndColorAlpha = 1f - fadeAmount + interpolatedRevealAmount = interpolatedAmount setRevealGradientBounds( width * (1f + OFF_SCREEN_START_AMOUNT) - width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount, @@ -284,6 +288,14 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } } + var interpolatedRevealAmount: Float = 1f + + val isScrimAlmostOccludes: Boolean + get() { + // if the interpolatedRevealAmount less than 0.1, over 90% of the screen is black. + return interpolatedRevealAmount < 0.1f + } + private fun updateScrimOpaque() { isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt index a1d086b5d768..73d3e2afac7c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt @@ -59,8 +59,8 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) { } vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) { - return p + vec2(sin(p.x * frequency + in_phase1), - cos(p.y * frequency * 1.23 + in_phase2)) * distort_amount_xy; + return p + vec2(sin(p.y * frequency + in_phase1), + cos(p.x * frequency * -1.23 + in_phase2)) * distort_amount_xy; } vec4 ripple(vec2 p, float distort_xy, float frequency) { @@ -73,11 +73,11 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) { """ private const val SHADER_MAIN = """vec4 main(vec2 p) { vec4 color1 = ripple(p, - 12 * in_distortion_strength, // distort_xy + 34 * in_distortion_strength, // distort_xy 0.012 // frequency ); vec4 color2 = ripple(p, - 17.5 * in_distortion_strength, // distort_xy + 49 * in_distortion_strength, // distort_xy 0.018 // frequency ); // Alpha blend between two layers. @@ -128,8 +128,8 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) { set(value) { field = value * 0.001f setUniform("in_time", field) - setUniform("in_phase1", field * 2f + 0.367f) - setUniform("in_phase2", field * 5.2f * 1.531f) + setUniform("in_phase1", field * 3f + 0.367f) + setUniform("in_phase2", field * 7.2f * 1.531f) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index d87a02493a23..1b42b58a55aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.display.AmbientDisplayConfiguration; import android.os.PowerManager; @@ -26,7 +27,10 @@ import android.util.Log; import android.util.MathUtils; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; @@ -36,13 +40,18 @@ import com.android.systemui.doze.DozeScreenState; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.unfold.FoldAodAnimationController; +import com.android.systemui.unfold.SysUIUnfoldComponent; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import javax.inject.Inject; @@ -54,7 +63,8 @@ import javax.inject.Inject; public class DozeParameters implements TunerService.Tunable, com.android.systemui.plugins.statusbar.DozeParameters, - Dumpable { + Dumpable, ConfigurationController.ConfigurationListener, + StatusBarStateController.StateListener, FoldAodAnimationController.FoldAodAnimationStatus { private static final int MAX_DURATION = 60 * 1000; public static final boolean FORCE_NO_BLANKING = SystemProperties.getBoolean("debug.force_no_blanking", false); @@ -69,12 +79,30 @@ public class DozeParameters implements private final BatteryController mBatteryController; private final FeatureFlags mFeatureFlags; private final ScreenOffAnimationController mScreenOffAnimationController; + private final FoldAodAnimationController mFoldAodAnimationController; + private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private final Set<Callback> mCallbacks = new HashSet<>(); private boolean mDozeAlwaysOn; private boolean mControlScreenOffAnimation; + private boolean mKeyguardShowing; + @VisibleForTesting + final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onKeyguardVisibilityChanged(boolean showing) { + mKeyguardShowing = showing; + updateControlScreenOff(); + } + + @Override + public void onShadeExpandedChanged(boolean expanded) { + updateControlScreenOff(); + } + }; + @Inject protected DozeParameters( @Main Resources resources, @@ -85,7 +113,12 @@ public class DozeParameters implements TunerService tunerService, DumpManager dumpManager, FeatureFlags featureFlags, - ScreenOffAnimationController screenOffAnimationController) { + ScreenOffAnimationController screenOffAnimationController, + Optional<SysUIUnfoldComponent> sysUiUnfoldComponent, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, + KeyguardUpdateMonitor keyguardUpdateMonitor, + ConfigurationController configurationController, + StatusBarStateController statusBarStateController) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; @@ -97,11 +130,22 @@ public class DozeParameters implements mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation); mFeatureFlags = featureFlags; mScreenOffAnimationController = screenOffAnimationController; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; + keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); tunerService.addTunable( this, Settings.Secure.DOZE_ALWAYS_ON, Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); + configurationController.addCallback(this); + statusBarStateController.addCallback(this); + + mFoldAodAnimationController = sysUiUnfoldComponent + .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); + + if (mFoldAodAnimationController != null) { + mFoldAodAnimationController.addCallback(this); + } } public boolean getDisplayStateSupported() { @@ -222,13 +266,26 @@ public class DozeParameters implements mPowerManager.setDozeAfterScreenOff(!controlScreenOffAnimation); } + public void updateControlScreenOff() { + if (!getDisplayNeedsBlanking()) { + final boolean controlScreenOff = + getAlwaysOn() && (mKeyguardShowing || shouldControlUnlockedScreenOff()); + setControlScreenOffAnimation(controlScreenOff); + } + } + /** * Whether we want to control the screen off animation when the device is unlocked. If we do, * we'll animate in AOD before turning off the screen, rather than simply fading to black and * then abruptly showing AOD. + * + * There are currently several reasons we might not want to control the screen off even if we + * are able to, such as the shade being expanded, being in landscape, or having animations + * disabled for a11y. */ public boolean shouldControlUnlockedScreenOff() { - return mScreenOffAnimationController.shouldControlUnlockedScreenOff(); + return canControlUnlockedScreenOff() + && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation(); } public boolean shouldDelayKeyguardShow() { @@ -325,6 +382,11 @@ public class DozeParameters implements @Override public void onTuningChanged(String key, String newValue) { mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); + + if (key.equals(Settings.Secure.DOZE_ALWAYS_ON)) { + updateControlScreenOff(); + } + for (Callback callback : mCallbacks) { callback.onAlwaysOnChange(); } @@ -332,6 +394,21 @@ public class DozeParameters implements } @Override + public void onConfigChanged(Configuration newConfig) { + updateControlScreenOff(); + } + + @Override + public void onStatePostChange() { + updateControlScreenOff(); + } + + @Override + public void onFoldToAodAnimationChanged() { + updateControlScreenOff(); + } + + @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn()); pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 57b9c03ce576..a88a3b6392c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -63,7 +63,6 @@ public final class DozeServiceHost implements DozeHost { private final DozeLog mDozeLog; private final PowerManager mPowerManager; private boolean mAnimateWakeup; - private boolean mAnimateScreenOff; private boolean mIgnoreTouchWhilePulsing; private Runnable mPendingScreenOffCallback; @VisibleForTesting @@ -357,11 +356,6 @@ public final class DozeServiceHost implements DozeHost { } @Override - public void setAnimateScreenOff(boolean animateScreenOff) { - mAnimateScreenOff = animateScreenOff; - } - - @Override public void onSlpiTap(float screenX, float screenY) { if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) { @@ -440,10 +434,6 @@ public final class DozeServiceHost implements DozeHost { return mAnimateWakeup; } - boolean shouldAnimateScreenOff() { - return mAnimateScreenOff; - } - boolean getIgnoreTouchWhilePulsing() { return mIgnoreTouchWhilePulsing; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 07914cf1d9c4..2ba70df8a1da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3162,7 +3162,7 @@ public class StatusBar extends CoreStartable implements boolean wakeAndUnlock = mBiometricUnlockController.getMode() == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock) - || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() + || (mDozing && mDozeParameters.shouldControlScreenOff() && visibleNotOccludedOrWillBe); mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index fc661b920837..0ba713464b80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -9,6 +9,9 @@ import android.os.Handler import android.provider.Settings import android.view.Surface import android.view.View +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF +import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator @@ -50,7 +53,8 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, private val keyguardStateController: KeyguardStateController, private val dozeParameters: dagger.Lazy<DozeParameters>, - private val globalSettings: GlobalSettings + private val globalSettings: GlobalSettings, + private val interactionJankMonitor: InteractionJankMonitor ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { private val handler = Handler() @@ -78,17 +82,28 @@ class UnlockedScreenOffAnimationController @Inject constructor( sendUnlockedScreenOffProgressUpdate( 1f - (it.animatedFraction as Float), 1f - (it.animatedValue as Float)) + if (lightRevealScrim.isScrimAlmostOccludes && + interactionJankMonitor.isInstrumenting(CUJ_SCREEN_OFF)) { + // ends the instrument when the scrim almost occludes the screen. + // because the following janky frames might not be perceptible. + interactionJankMonitor.end(CUJ_SCREEN_OFF) + } } addListener(object : AnimatorListenerAdapter() { override fun onAnimationCancel(animation: Animator?) { lightRevealScrim.revealAmount = 1f lightRevealAnimationPlaying = false sendUnlockedScreenOffProgressUpdate(0f, 0f) + interactionJankMonitor.cancel(CUJ_SCREEN_OFF) } override fun onAnimationEnd(animation: Animator?) { lightRevealAnimationPlaying = false } + + override fun onAnimationStart(animation: Animator?) { + interactionJankMonitor.begin(statusBar.notificationShadeWindowView, CUJ_SCREEN_OFF) + } }) } @@ -163,6 +178,16 @@ class UnlockedScreenOffAnimationController @Inject constructor( decidedToAnimateGoingToSleep = null // We need to unset the listener. These are persistent for future animators keyguardView.animate().setListener(null) + interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) + } + + override fun onAnimationCancel(animation: Animator?) { + interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + } + + override fun onAnimationStart(animation: Animator?) { + interactionJankMonitor.begin( + statusBar.notificationShadeWindowView, CUJ_SCREEN_OFF_SHOW_AOD) } }) .start() @@ -229,10 +254,6 @@ class UnlockedScreenOffAnimationController @Inject constructor( return false } - if (!dozeParameters.get().canControlUnlockedScreenOff()) { - return false - } - // If animations are disabled system-wide, don't play this one either. if (Settings.Global.getString( context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE) == "0") { @@ -248,10 +269,10 @@ class UnlockedScreenOffAnimationController @Inject constructor( // already expanded and showing notifications/QS, the animation looks really messy. For now, // disable it if the notification panel is not fully collapsed. if ((!this::statusBar.isInitialized || - !statusBar.notificationPanelViewController.isFullyCollapsed) + !statusBar.notificationPanelViewController.isFullyCollapsed) && // Status bar might be expanded because we have started // playing the animation already - && !isAnimationPlaying() + !isAnimationPlaying() ) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 8e8a33fd0d9b..3831857c5c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static android.app.AppOpsManager.OP_COARSE_LOCATION; +import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static com.android.settingslib.Utils.updateLocationEnabled; @@ -30,11 +32,13 @@ import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.provider.Settings; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.BootCompleteCache; import com.android.systemui.appops.AppOpItem; import com.android.systemui.appops.AppOpsController; @@ -43,6 +47,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.Utils; import java.util.ArrayList; @@ -59,30 +64,47 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio private final Context mContext; private final AppOpsController mAppOpsController; + private final DeviceConfigProxy mDeviceConfigProxy; private final BootCompleteCache mBootCompleteCache; private final UserTracker mUserTracker; private final H mHandler; private boolean mAreActiveLocationRequests; + private boolean mShouldDisplayAllAccesses; @Inject public LocationControllerImpl(Context context, AppOpsController appOpsController, + DeviceConfigProxy deviceConfigProxy, @Main Looper mainLooper, @Background Handler backgroundHandler, BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache, UserTracker userTracker) { mContext = context; mAppOpsController = appOpsController; + mDeviceConfigProxy = deviceConfigProxy; mBootCompleteCache = bootCompleteCache; mHandler = new H(mainLooper); mUserTracker = userTracker; + mShouldDisplayAllAccesses = getDeviceConfigSetting(); + + // Register to listen for changes in DeviceConfig settings. + mDeviceConfigProxy.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_PRIVACY, + backgroundHandler::post, + properties -> { + mShouldDisplayAllAccesses = getDeviceConfigSetting(); + updateActiveLocationRequests(); + }); // Register to listen for changes in location settings. IntentFilter filter = new IntentFilter(); filter.addAction(LocationManager.MODE_CHANGED_ACTION); broadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, UserHandle.ALL); - mAppOpsController.addCallback(new int[]{OP_MONITOR_HIGH_POWER_LOCATION}, this); + // Listen to all accesses and filter the ones interested in based on flags. + mAppOpsController.addCallback( + new int[]{OP_COARSE_LOCATION, OP_FINE_LOCATION, OP_MONITOR_HIGH_POWER_LOCATION}, + this); // Examine the current location state and initialize the status view. backgroundHandler.post(this::updateActiveLocationRequests); @@ -154,6 +176,11 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio UserHandle.of(userId)); } + private boolean getDeviceConfigSetting() { + return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false); + } + /** * Returns true if there currently exist active high power location requests. */ @@ -171,10 +198,37 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio return false; } - // Reads the active location requests and updates the status view if necessary. + /** + * Returns true if there currently exist active location requests. + */ + @VisibleForTesting + protected boolean areActiveLocationRequests() { + if (!mShouldDisplayAllAccesses) { + return false; + } + List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); + + final int numItems = appOpsItems.size(); + for (int i = 0; i < numItems; i++) { + if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION + || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) { + return true; + } + } + + return false; + } + + // Reads the active location requests from either OP_MONITOR_HIGH_POWER_LOCATION, + // OP_FINE_LOCATION, or OP_COARSE_LOCATION and updates the status view if necessary. private void updateActiveLocationRequests() { boolean hadActiveLocationRequests = mAreActiveLocationRequests; - mAreActiveLocationRequests = areActiveHighPowerLocationRequests(); + if (mShouldDisplayAllAccesses) { + mAreActiveLocationRequests = + areActiveHighPowerLocationRequests() || areActiveLocationRequests(); + } else { + mAreActiveLocationRequests = areActiveHighPowerLocationRequests(); + } if (mAreActiveLocationRequests != hadActiveLocationRequests) { mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index e6fc49fae315..0b89ef28d227 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.PixelFormat import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener +import android.hardware.input.InputManager import android.hardware.display.DisplayManager import android.os.Handler import android.os.Trace @@ -195,8 +196,7 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS params.fitInsetsTypes = 0 - params.flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) + params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE params.setTrustedOverlay() val packageName: String = context.opPackageName @@ -239,6 +239,8 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( if (scrimView == null) { addView() } + // Disable input dispatching during transition. + InputManager.getInstance().cancelCurrentTouch() } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 4600bc71459a..31b17f8dd11d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -258,11 +258,6 @@ public final class WMShell extends CoreStartable public void onKeyguardVisibilityChanged(boolean showing) { splitScreen.onKeyguardVisibilityChanged(showing); } - - @Override - public void onKeyguardOccludedChanged(boolean occluded) { - splitScreen.onKeyguardOccludedChanged(occluded); - } }; mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback); @@ -271,11 +266,6 @@ public final class WMShell extends CoreStartable public void onFinishedWakingUp() { splitScreen.onFinishedWakingUp(); } - - @Override - public void onFinishedGoingToSleep() { - splitScreen.onFinishedGoingToSleep(); - } }); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 2c4808a4b84f..5128ccc15d9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -131,7 +131,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN update sensor location and show ripple - verify(rippleView).setSensorLocation(fpsLocation) + verify(rippleView).setFingerprintSensorLocation(fpsLocation, -1f) verify(rippleView).startUnlockedRipple(any()) } @@ -292,10 +292,10 @@ class AuthRippleControllerTest : SysuiTestCase() { reset(rippleView) captor.value.onThemeChanged() - verify(rippleView).setColor(ArgumentMatchers.anyInt()) + verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt()) reset(rippleView) captor.value.onUiModeChanged() - verify(rippleView).setColor(ArgumentMatchers.anyInt()) + verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java index 55af51d3fddf..e5a75e231f8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java @@ -27,8 +27,6 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,10 +41,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; -import com.android.systemui.unfold.FoldAodAnimationController; -import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.wakelock.WakeLockFake; import org.junit.After; @@ -56,8 +51,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Optional; - @RunWith(AndroidJUnit4.class) @SmallTest public class DozeUiTest extends SysuiTestCase { @@ -82,12 +75,6 @@ public class DozeUiTest extends SysuiTestCase { private DozeUi mDozeUi; @Mock private StatusBarStateController mStatusBarStateController; - @Mock - private FoldAodAnimationController mFoldAodAnimationController; - @Mock - private SysUIUnfoldComponent mSysUIUnfoldComponent; - @Mock - private ConfigurationController mConfigurationController; @Before public void setUp() throws Exception { @@ -98,13 +85,8 @@ public class DozeUiTest extends SysuiTestCase { mWakeLock = new WakeLockFake(); mHandler = mHandlerThread.getThreadHandler(); - when(mSysUIUnfoldComponent.getFoldAodAnimationController()) - .thenReturn(mFoldAodAnimationController); - mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, - mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - mStatusBarStateController, Optional.of(mSysUIUnfoldComponent), - mConfigurationController); + mDozeParameters, mKeyguardUpdateMonitor, mStatusBarStateController, mDozeLog); mDozeUi.setDozeMachine(mMachine); } @@ -116,7 +98,7 @@ public class DozeUiTest extends SysuiTestCase { } @Test - public void pausingAndUnpausingAod_registersTimeTickAfterUnpausing() throws Exception { + public void pausingAndUnpausingAod_registersTimeTickAfterUnpausing() { mDozeUi.transitionTo(UNINITIALIZED, INITIALIZED); mDozeUi.transitionTo(INITIALIZED, DOZE_AOD); mDozeUi.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED); @@ -129,60 +111,9 @@ public class DozeUiTest extends SysuiTestCase { } @Test - public void propagatesAnimateScreenOff_noAlwaysOn() { - reset(mHost); - when(mDozeParameters.getAlwaysOn()).thenReturn(false); - when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false); - when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true); - - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false); - verify(mHost).setAnimateScreenOff(eq(false)); - } - - @Test - public void propagatesAnimateScreenOff_alwaysOn() { - reset(mHost); - when(mDozeParameters.getAlwaysOn()).thenReturn(true); - when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false); - when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true); - - // Take over when the keyguard is visible. - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true); - verify(mHost).setAnimateScreenOff(eq(true)); - - // Do not animate screen-off when keyguard isn't visible - PowerManager will do it. - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false); - verify(mHost).setAnimateScreenOff(eq(false)); - } - - @Test - public void propagatesAnimateScreenOff_alwaysOn_shouldAnimateDozingChangeIsFalse() { - reset(mHost); - when(mDozeParameters.getAlwaysOn()).thenReturn(true); - when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false); - when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(false); - - // Take over when the keyguard is visible. - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true); - verify(mHost).setAnimateScreenOff(eq(false)); - } - - @Test - public void neverAnimateScreenOff_whenNotSupported() { - // Re-initialize DozeParameters saying that the display requires blanking. - reset(mDozeParameters); - reset(mHost); - when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true); - mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler, - mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService, - mStatusBarStateController, Optional.of(mSysUIUnfoldComponent), - mConfigurationController); - mDozeUi.setDozeMachine(mMachine); - - // Never animate if display doesn't support it. - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true); - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false); - verify(mHost, never()).setAnimateScreenOff(eq(false)); + public void transitionSetsAnimateWakeup_noAlwaysOn() { + mDozeUi.transitionTo(UNINITIALIZED, DOZE); + verify(mHost).setAnimateWakeup(eq(false)); } @Test @@ -192,49 +123,4 @@ public class DozeUiTest extends SysuiTestCase { mDozeUi.transitionTo(UNINITIALIZED, DOZE); verify(mHost).setAnimateWakeup(eq(true)); } - - @Test - public void keyguardVisibility_changesControlScreenOffAnimation() { - // Pre-condition - reset(mDozeParameters); - when(mDozeParameters.getAlwaysOn()).thenReturn(true); - when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false); - - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false); - verify(mDozeParameters).setControlScreenOffAnimation(eq(false)); - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true); - verify(mDozeParameters).setControlScreenOffAnimation(eq(true)); - } - - @Test - public void transitionSetsAnimateWakeup_noAlwaysOn() { - mDozeUi.transitionTo(UNINITIALIZED, DOZE); - verify(mHost).setAnimateWakeup(eq(false)); - } - - @Test - public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() { - when(mDozeParameters.getAlwaysOn()).thenReturn(true); - when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); - - // Tell doze that keyguard is not visible. - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */); - - // Since we're controlling the unlocked screen off animation, verify that we've asked to - // control the screen off animation despite being unlocked. - verify(mDozeParameters).setControlScreenOffAnimation(true); - } - - @Test - public void controlScreenOffFalseWhenKeyguardNotShowingAndControlUnlockedScreenOffFalse() { - when(mDozeParameters.getAlwaysOn()).thenReturn(true); - when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false); - - // Tell doze that keyguard is not visible. - mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */); - - // Since we're not controlling the unlocked screen off animation, verify that we haven't - // asked to control the screen off animation since we're unlocked. - verify(mDozeParameters).setControlScreenOffAnimation(false); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt index 87499172fc46..de6525a2e750 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt @@ -16,8 +16,11 @@ package com.android.systemui.media.taptotransfer +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon import android.view.View import android.view.WindowManager +import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.test.filters.SmallTest @@ -36,10 +39,11 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.concurrent.Future @SmallTest class MediaTttChipControllerTest : SysuiTestCase() { - + private lateinit var appIconDrawable: Drawable private lateinit var fakeMainClock: FakeSystemClock private lateinit var fakeMainExecutor: FakeExecutor private lateinit var fakeBackgroundClock: FakeSystemClock @@ -53,6 +57,7 @@ class MediaTttChipControllerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) fakeMainClock = FakeSystemClock() fakeMainExecutor = FakeExecutor(fakeMainClock) fakeBackgroundClock = FakeSystemClock() @@ -64,24 +69,24 @@ class MediaTttChipControllerTest : SysuiTestCase() { @Test fun displayChip_chipAdded() { - mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME)) + mediaTttChipController.displayChip(moveCloserToTransfer()) verify(windowManager).addView(any(), any()) } @Test fun displayChip_twice_chipNotAddedTwice() { - mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME)) + mediaTttChipController.displayChip(moveCloserToTransfer()) reset(windowManager) - mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME)) + mediaTttChipController.displayChip(moveCloserToTransfer()) verify(windowManager, never()).addView(any(), any()) } @Test fun removeChip_chipRemoved() { // First, add the chip - mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME)) + mediaTttChipController.displayChip(moveCloserToTransfer()) // Then, remove it mediaTttChipController.removeChip() @@ -97,24 +102,26 @@ class MediaTttChipControllerTest : SysuiTestCase() { } @Test - fun moveCloserToTransfer_chipTextContainsDeviceName_noLoadingIcon_noUndo() { - mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME)) + fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() { + mediaTttChipController.displayChip(moveCloserToTransfer()) val chipView = getChipView() + assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable) assertThat(chipView.getChipText()).contains(DEVICE_NAME) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) } @Test - fun transferInitiated_futureNotResolvedYet_loadingIcon_noUndo() { + fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() { val future: SettableFuture<Runnable?> = SettableFuture.create() - mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future)) + mediaTttChipController.displayChip(transferInitiated(future)) // Don't resolve the future in any way and don't run our executors // Assert we're still in the loading state val chipView = getChipView() + assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable) assertThat(chipView.getChipText()).contains(DEVICE_NAME) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -125,7 +132,7 @@ class MediaTttChipControllerTest : SysuiTestCase() { val future: SettableFuture<Runnable?> = SettableFuture.create() val undoRunnable = Runnable { } - mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future)) + mediaTttChipController.displayChip(transferInitiated(future)) future.set(undoRunnable) fakeBackgroundExecutor.advanceClockToLast() @@ -146,7 +153,7 @@ class MediaTttChipControllerTest : SysuiTestCase() { fun transferInitiated_futureCancelled_chipRemoved() { val future: SettableFuture<Runnable?> = SettableFuture.create() - mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future)) + mediaTttChipController.displayChip(transferInitiated(future)) future.cancel(true) fakeBackgroundExecutor.advanceClockToLast() @@ -163,7 +170,7 @@ class MediaTttChipControllerTest : SysuiTestCase() { @Test fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() { val future: SettableFuture<Runnable?> = SettableFuture.create() - mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future)) + mediaTttChipController.displayChip(transferInitiated(future)) // We won't set anything on the future, but we will still run the executors so that we're // waiting on the future resolving. If we have a bug in our code, then this test will time @@ -180,22 +187,28 @@ class MediaTttChipControllerTest : SysuiTestCase() { } @Test - fun transferSucceededNullUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_noUndo() { - mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, undoRunnable = null)) + fun transferSucceeded_appIcon_chipTextContainsDeviceName_noLoadingIcon() { + mediaTttChipController.displayChip(transferSucceeded()) val chipView = getChipView() + assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable) assertThat(chipView.getChipText()).contains(DEVICE_NAME) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) + } + + @Test + fun transferSucceededNullUndoRunnable_noUndo() { + mediaTttChipController.displayChip(transferSucceeded(undoRunnable = null)) + + val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) } @Test - fun transferSucceededWithUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_undoWithClick() { - mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { }) + fun transferSucceededWithUndoRunnable_undoWithClick() { + mediaTttChipController.displayChip(transferSucceeded { }) val chipView = getChipView() - assertThat(chipView.getChipText()).contains(DEVICE_NAME) - assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue() } @@ -205,7 +218,7 @@ class MediaTttChipControllerTest : SysuiTestCase() { var runnableRun = false val runnable = Runnable { runnableRun = true } - mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, runnable)) + mediaTttChipController.displayChip(transferSucceeded(undoRunnable = runnable)) getChipView().getUndoButton().performClick() assertThat(runnableRun).isTrue() @@ -213,36 +226,39 @@ class MediaTttChipControllerTest : SysuiTestCase() { @Test fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() { - mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME)) - mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE)) + mediaTttChipController.displayChip(moveCloserToTransfer()) + mediaTttChipController.displayChip(transferInitiated()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE) } @Test fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() { - mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE)) - mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME)) + mediaTttChipController.displayChip(transferInitiated()) + mediaTttChipController.displayChip(transferSucceeded()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE) } @Test fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() { - mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE)) - mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { }) + mediaTttChipController.displayChip(transferInitiated()) + mediaTttChipController.displayChip(transferSucceeded { }) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE) } @Test fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() { - mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME)) - mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME)) + mediaTttChipController.displayChip(transferSucceeded()) + mediaTttChipController.displayChip(moveCloserToTransfer()) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE) } + private fun LinearLayout.getAppIconDrawable(): Drawable = + (this.requireViewById<ImageView>(R.id.app_icon)).drawable + private fun LinearLayout.getChipText(): String = (this.requireViewById<TextView>(R.id.text)).text as String @@ -256,6 +272,19 @@ class MediaTttChipControllerTest : SysuiTestCase() { verify(windowManager).addView(viewCaptor.capture(), any()) return viewCaptor.value as LinearLayout } + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun moveCloserToTransfer() = MoveCloserToTransfer(DEVICE_NAME, appIconDrawable) + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferInitiated( + future: Future<Runnable?> = TEST_FUTURE + ) = TransferInitiated(DEVICE_NAME, appIconDrawable, future) + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferSucceeded( + undoRunnable: Runnable? = null + ) = TransferSucceeded(DEVICE_NAME, appIconDrawable, undoRunnable) } private const val DEVICE_NAME = "My Tablet" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 9b2d3eb17568..91b529875654 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -49,7 +49,7 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) mediaTttCommandLineHelper = MediaTttCommandLineHelper( - commandRegistry, mediaTttChipController, FakeExecutor(FakeSystemClock()) + commandRegistry, context, mediaTttChipController, FakeExecutor(FakeSystemClock()) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index e4db07220949..a14ea54fc7e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -17,11 +17,12 @@ package com.android.systemui.statusbar.phone; import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import android.content.res.Resources; @@ -32,14 +33,19 @@ import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.unfold.FoldAodAnimationController; +import com.android.systemui.unfold.SysUIUnfoldComponent; import org.junit.Assert; import org.junit.Before; @@ -48,10 +54,11 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + @SmallTest @RunWith(AndroidJUnit4.class) public class DozeParametersTest extends SysuiTestCase { - private DozeParameters mDozeParameters; @Mock Resources mResources; @@ -63,10 +70,37 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private FeatureFlags mFeatureFlags; @Mock private DumpManager mDumpManager; @Mock private ScreenOffAnimationController mScreenOffAnimationController; + @Mock private FoldAodAnimationController mFoldAodAnimationController; + @Mock private SysUIUnfoldComponent mSysUIUnfoldComponent; + @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private StatusBarStateController mStatusBarStateController; + @Mock private ConfigurationController mConfigurationController; + + /** + * The current value of PowerManager's dozeAfterScreenOff property. + * + * This property controls whether System UI is controlling the screen off animation. If it's + * false (PowerManager should not doze after screen off) then System UI is controlling the + * animation. If true, we're not controlling it and PowerManager will doze immediately. + */ + private boolean mPowerManagerDozeAfterScreenOff; @Before public void setup() { MockitoAnnotations.initMocks(this); + + // Save the current value set for dozeAfterScreenOff so we can make assertions. This method + // is only called if the value changes, which makes it difficult to check that it was set + // correctly in tests. + doAnswer(invocation -> { + mPowerManagerDozeAfterScreenOff = invocation.getArgument(0); + return mPowerManagerDozeAfterScreenOff; + }).when(mPowerManager).setDozeAfterScreenOff(anyBoolean()); + + when(mSysUIUnfoldComponent.getFoldAodAnimationController()) + .thenReturn(mFoldAodAnimationController); + mDozeParameters = new DozeParameters( mResources, mAmbientDisplayConfiguration, @@ -76,23 +110,31 @@ public class DozeParametersTest extends SysuiTestCase { mTunerService, mDumpManager, mFeatureFlags, - mScreenOffAnimationController + mScreenOffAnimationController, + Optional.of(mSysUIUnfoldComponent), + mUnlockedScreenOffAnimationController, + mKeyguardUpdateMonitor, + mConfigurationController, + mStatusBarStateController ); + + when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(true); + setAodEnabledForTest(true); + setShouldControlUnlockedScreenOffForTest(true); + setDisplayNeedsBlankingForTest(false); } + @Test - public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_false() { + public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_correctly() { + // If we want to control screen off, we do NOT want PowerManager to doze after screen off. + // Obviously. mDozeParameters.setControlScreenOffAnimation(true); - reset(mPowerManager); - mDozeParameters.setControlScreenOffAnimation(false); - verify(mPowerManager).setDozeAfterScreenOff(eq(true)); - } + assertFalse(mPowerManagerDozeAfterScreenOff); - @Test - public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_true() { + // If we don't want to control screen off, PowerManager is free to doze after screen off if + // that's what'll make it happy. mDozeParameters.setControlScreenOffAnimation(false); - reset(mPowerManager); - mDozeParameters.setControlScreenOffAnimation(true); - verify(mPowerManager).setDozeAfterScreenOff(eq(false)); + assertTrue(mPowerManagerDozeAfterScreenOff); } @Test @@ -121,35 +163,124 @@ public class DozeParametersTest extends SysuiTestCase { assertThat(mDozeParameters.getAlwaysOn()).isFalse(); } + /** + * PowerManager.setDozeAfterScreenOff(true) means we are not controlling screen off, and calling + * it with false means we are. Confusing, but sure - make sure that we call PowerManager with + * the correct value depending on whether we want to control screen off. + */ @Test public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() { - when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); - mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); - when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(true); - when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); + // If AOD is disabled, we shouldn't want to control screen off. Also, let's double check + // that when that value is updated, we called through to PowerManager. + setAodEnabledForTest(false); + assertFalse(mDozeParameters.shouldControlScreenOff()); + assertTrue(mPowerManagerDozeAfterScreenOff); - // Trigger the setter for the current value. - mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff()); - - // We should have asked power manager not to doze after screen off no matter what, since - // we're animating and controlling screen off. - verify(mPowerManager).setDozeAfterScreenOff(eq(false)); + // And vice versa... + setAodEnabledForTest(true); + assertTrue(mDozeParameters.shouldControlScreenOff()); + assertFalse(mPowerManagerDozeAfterScreenOff); } @Test public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() { - when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); - mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); + setShouldControlUnlockedScreenOffForTest(true); when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false); assertFalse(mDozeParameters.shouldControlUnlockedScreenOff()); // Trigger the setter for the current value. mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff()); + assertFalse(mDozeParameters.shouldControlScreenOff()); + } + + @Test + public void propagatesAnimateScreenOff_noAlwaysOn() { + setAodEnabledForTest(false); + setDisplayNeedsBlankingForTest(false); + + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false); + assertFalse(mDozeParameters.shouldControlScreenOff()); + } + + @Test + public void propagatesAnimateScreenOff_alwaysOn() { + setAodEnabledForTest(true); + setDisplayNeedsBlankingForTest(false); + setShouldControlUnlockedScreenOffForTest(false); + + // Take over when the keyguard is visible. + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true); + assertTrue(mDozeParameters.shouldControlScreenOff()); + + // Do not animate screen-off when keyguard isn't visible. + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false); + assertFalse(mDozeParameters.shouldControlScreenOff()); + } + + + @Test + public void neverAnimateScreenOff_whenNotSupported() { + setDisplayNeedsBlankingForTest(true); + + // Never animate if display doesn't support it. + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true); + assertFalse(mDozeParameters.shouldControlScreenOff()); + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false); + assertFalse(mDozeParameters.shouldControlScreenOff()); + } + + + @Test + public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() { + setShouldControlUnlockedScreenOffForTest(true); + + // Tell doze that keyguard is not visible. + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged( + false /* showing */); + + // Since we're controlling the unlocked screen off animation, verify that we've asked to + // control the screen off animation despite being unlocked. + assertTrue(mDozeParameters.shouldControlScreenOff()); + } + + + @Test + public void keyguardVisibility_changesControlScreenOffAnimation() { + setShouldControlUnlockedScreenOffForTest(false); + + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false); + assertFalse(mDozeParameters.shouldControlScreenOff()); + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true); + assertTrue(mDozeParameters.shouldControlScreenOff()); + } + + @Test + public void keyguardVisibility_changesControlScreenOffAnimation_respectsUnlockedScreenOff() { + setShouldControlUnlockedScreenOffForTest(true); + + // Even if the keyguard is gone, we should control screen off if we can control unlocked + // screen off. + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(false); + assertTrue(mDozeParameters.shouldControlScreenOff()); + + mDozeParameters.mKeyguardVisibilityCallback.onKeyguardVisibilityChanged(true); + assertTrue(mDozeParameters.shouldControlScreenOff()); + } + + private void setDisplayNeedsBlankingForTest(boolean needsBlanking) { + when(mResources.getBoolean( + com.android.internal.R.bool.config_displayBlanksAfterDoze)).thenReturn( + needsBlanking); + } + + private void setAodEnabledForTest(boolean enabled) { + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(enabled); + mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, ""); + } - // We should have asked power manager to doze only if we're not controlling screen off - // normally. - verify(mPowerManager).setDozeAfterScreenOff( - eq(!mDozeParameters.shouldControlScreenOff())); + private void setShouldControlUnlockedScreenOffForTest(boolean shouldControl) { + when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation()) + .thenReturn(shouldControl); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index a8a33dabf1aa..24a56bc76fae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle @@ -59,6 +60,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var statusBarStateController: StatusBarStateControllerImpl + @Mock + private lateinit var interactionJankMonitor: InteractionJankMonitor @Before fun setUp() { @@ -71,7 +74,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator }, keyguardStateController, dagger.Lazy<DozeParameters> { dozeParameters }, - globalSettings + globalSettings, + interactionJankMonitor ) controller.initialize(statusbar, lightRevealScrim) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index be836d4132f2..087f2e6006cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -15,9 +15,7 @@ package com.android.systemui.statusbar.policy; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -27,18 +25,25 @@ import android.content.Intent; import android.location.LocationManager; import android.os.Handler; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.BootCompleteCache; import com.android.systemui.SysuiTestCase; +import com.android.systemui.appops.AppOpItem; import com.android.systemui.appops.AppOpsController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; +import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.DeviceConfigProxyFake; + +import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Test; @@ -53,6 +58,7 @@ public class LocationControllerImplTest extends SysuiTestCase { private LocationControllerImpl mLocationController; private TestableLooper mTestableLooper; + private DeviceConfigProxy mDeviceConfigProxy; @Mock private AppOpsController mAppOpsController; @Mock private UserTracker mUserTracker; @@ -62,15 +68,17 @@ public class LocationControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); + mDeviceConfigProxy = new DeviceConfigProxyFake(); mTestableLooper = TestableLooper.get(this); - mLocationController = spy(new LocationControllerImpl(mContext, + mLocationController = new LocationControllerImpl(mContext, mAppOpsController, + mDeviceConfigProxy, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), mock(BroadcastDispatcher.class), mock(BootCompleteCache.class), - mUserTracker)); + mUserTracker); mTestableLooper.processAllMessages(); } @@ -86,10 +94,13 @@ public class LocationControllerImplTest extends SysuiTestCase { mLocationController.addCallback(callback); mLocationController.addCallback(mock(LocationChangeCallback.class)); - doReturn(false).when(mLocationController).areActiveHighPowerLocationRequests(); + when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0, "", false); - doReturn(true).when(mLocationController).areActiveHighPowerLocationRequests(); + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0, "", + System.currentTimeMillis()))); mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0, "", true); @@ -135,7 +146,10 @@ public class LocationControllerImplTest extends SysuiTestCase { verify(callback, times(2)).onLocationSettingsChanged(anyBoolean()); - doReturn(true).when(mLocationController).areActiveHighPowerLocationRequests(); + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0, "", + System.currentTimeMillis()))); mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0, "", true); @@ -145,6 +159,46 @@ public class LocationControllerImplTest extends SysuiTestCase { } @Test + public void testCallbackNotified_additionalOps() { + LocationChangeCallback callback = mock(LocationChangeCallback.class); + + mLocationController.addCallback(callback); + + mTestableLooper.processAllMessages(); + + mLocationController.onReceive(mContext, new Intent(LocationManager.MODE_CHANGED_ACTION)); + + mTestableLooper.processAllMessages(); + + verify(callback, times(2)).onLocationSettingsChanged(anyBoolean()); + + mDeviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, + "true", + true); + mTestableLooper.processAllMessages(); + + when(mAppOpsController.getActiveAppOps()) + .thenReturn(ImmutableList.of( + new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "", + System.currentTimeMillis()))); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "", true); + + mTestableLooper.processAllMessages(); + + verify(callback, times(1)).onLocationActiveChanged(true); + + when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of()); + mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0, + "", false); + mTestableLooper.processAllMessages(); + + verify(callback, times(1)).onLocationActiveChanged(false); + } + + @Test public void testCallbackRemoved() { LocationChangeCallback callback = mock(LocationChangeCallback.class); diff --git a/services/core/java/com/android/server/app/GameClassifier.java b/services/core/java/com/android/server/app/GameClassifier.java new file mode 100644 index 000000000000..e20bf46e3b42 --- /dev/null +++ b/services/core/java/com/android/server/app/GameClassifier.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.os.UserHandle; + +/** + * Responsible for determining if a given application is a game. + */ +interface GameClassifier { + + /** + * Returns {@code true} if the application associated with the given {@code packageName} is + * considered to be a game. The application is queried as the user associated with the given + * {@code userHandle}. + */ + boolean isGame(@NonNull String packageName, @NonNull UserHandle userHandle); +} diff --git a/services/core/java/com/android/server/app/GameClassifierImpl.java b/services/core/java/com/android/server/app/GameClassifierImpl.java new file mode 100644 index 000000000000..8f5b0f0a5f42 --- /dev/null +++ b/services/core/java/com/android/server/app/GameClassifierImpl.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.UserHandle; + +final class GameClassifierImpl implements GameClassifier { + + private final PackageManager mPackageManager; + + GameClassifierImpl(@NonNull PackageManager packageManager) { + mPackageManager = packageManager; + } + + @Override + public boolean isGame(@NonNull String packageName, @NonNull UserHandle userHandle) { + @ApplicationInfo.Category + int applicationCategory = ApplicationInfo.CATEGORY_UNDEFINED; + + try { + applicationCategory = + mPackageManager.getApplicationInfoAsUser( + packageName, + 0, + userHandle.getIdentifier()).category; + } catch (PackageManager.NameNotFoundException ex) { + return false; + } + + return applicationCategory == ApplicationInfo.CATEGORY_GAME; + } +} diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index fc48cd5507a8..0980f4077489 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -76,6 +76,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; @@ -563,7 +564,12 @@ public final class GameManagerService extends IGameManagerService.Stub { mService.registerPackageReceiver(); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { - mGameServiceController = new GameServiceController(context); + mGameServiceController = new GameServiceController( + BackgroundThread.getExecutor(), + new GameServiceProviderSelectorImpl( + getContext().getResources(), + getContext().getPackageManager()), + new GameServiceProviderInstanceFactoryImpl(getContext())); } } @@ -586,6 +592,14 @@ public final class GameManagerService extends IGameManagerService.Stub { } @Override + public void onUserUnlocking(@NonNull TargetUser user) { + super.onUserUnlocking(user); + if (mGameServiceController != null) { + mGameServiceController.notifyUserUnlocking(user); + } + } + + @Override public void onUserStopping(@NonNull TargetUser user) { mService.onUserStopping(user.getUserIdentifier()); if (mGameServiceController != null) { diff --git a/services/core/java/com/android/server/app/GameServiceConnection.java b/services/core/java/com/android/server/app/GameServiceConnection.java deleted file mode 100644 index b60778915de7..000000000000 --- a/services/core/java/com/android/server/app/GameServiceConnection.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2021 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.app; - -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.UserHandle; -import android.service.games.GameService; -import android.service.games.IGameService; -import android.util.Slog; - -final class GameServiceConnection { - private static final String TAG = "GameServiceConnection"; - private static final boolean DEBUG = false; - - private final Context mContext; - private final ComponentName mGameServiceComponent; - private final int mUser; - private boolean mIsBound; - @Nullable - private IGameService mGameService; - private final ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) { - Slog.d(TAG, "onServiceConnected to " + name + " for user(" + mUser + ")"); - } - - mGameService = IGameService.Stub.asInterface(service); - try { - mGameService.connected(); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException while calling ready", e); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) { - Slog.d(TAG, "onServiceDisconnected to " + name); - } - - mGameService = null; - } - }; - - GameServiceConnection(Context context, ComponentName gameServiceComponent, int user) { - mContext = context; - mGameServiceComponent = gameServiceComponent; - mUser = user; - } - - public void connect() { - if (mIsBound) { - Slog.v(TAG, "Already bound, ignoring start."); - return; - } - - Intent intent = new Intent(GameService.SERVICE_INTERFACE); - intent.setComponent(mGameServiceComponent); - mIsBound = mContext.bindServiceAsUser(intent, mConnection, - Context.BIND_AUTO_CREATE - | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, new UserHandle(mUser)); - if (!mIsBound) { - Slog.w(TAG, "Failed binding to game service " + mGameServiceComponent); - } - } - - public void disconnect() { - try { - if (mGameService != null) { - mGameService.disconnected(); - } - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in shutdown", e); - } - - if (mIsBound) { - mContext.unbindService(mConnection); - mIsBound = false; - } - } -} diff --git a/services/core/java/com/android/server/app/GameServiceController.java b/services/core/java/com/android/server/app/GameServiceController.java index d056ea9f359a..ac720b9c2971 100644 --- a/services/core/java/com/android/server/app/GameServiceController.java +++ b/services/core/java/com/android/server/app/GameServiceController.java @@ -18,40 +18,56 @@ package com.android.server.app; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.service.games.GameService; -import android.text.TextUtils; +import android.annotation.WorkerThread; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.server.SystemService; -import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +/** + * Responsible for managing the Game Service API. + * + * Key responsibilities selecting the active Game Service provider, binding to the Game Service + * provider services, and driving the GameService/GameSession lifecycles. + */ final class GameServiceController { private static final String TAG = "GameServiceController"; - private static final boolean DEBUG = false; - private final Context mContext; - @Nullable - private SystemService.TargetUser mCurrentForegroundUser; - private boolean mHasBootCompleted; - @Nullable - private GameServiceConnection mGameServiceConnection; + private final Object mLock = new Object(); + private final Executor mBackgroundExecutor; + private final GameServiceProviderSelector mGameServiceProviderSelector; + private final GameServiceProviderInstanceFactory mGameServiceProviderInstanceFactory; - GameServiceController(Context context) { - mContext = context; + private volatile boolean mHasBootCompleted; + @Nullable + private volatile SystemService.TargetUser mCurrentForegroundUser; + @GuardedBy("mLock") + @Nullable + private volatile GameServiceProviderConfiguration mActiveGameServiceProviderConfiguration; + @GuardedBy("mLock") + @Nullable + private volatile GameServiceProviderInstance mGameServiceProviderInstance; + + GameServiceController( + @NonNull Executor backgroundExecutor, + @NonNull GameServiceProviderSelector gameServiceProviderSelector, + @NonNull GameServiceProviderInstanceFactory gameServiceProviderInstanceFactory) { + mGameServiceProviderInstanceFactory = gameServiceProviderInstanceFactory; + mBackgroundExecutor = backgroundExecutor; + mGameServiceProviderSelector = gameServiceProviderSelector; } void onBootComplete() { + if (mHasBootCompleted) { + return; + } mHasBootCompleted = true; - evaluateGameServiceConnection(); + mBackgroundExecutor.execute(this::evaluateActiveGameServiceProvider); } void notifyUserStarted(@NonNull SystemService.TargetUser user) { @@ -59,96 +75,86 @@ final class GameServiceController { return; } - mCurrentForegroundUser = user; - evaluateGameServiceConnection(); + setCurrentForegroundUserAndEvaluateProvider(user); } void notifyNewForegroundUser(@NonNull SystemService.TargetUser user) { - mCurrentForegroundUser = user; - evaluateGameServiceConnection(); + setCurrentForegroundUserAndEvaluateProvider(user); } - void notifyUserStopped(@NonNull SystemService.TargetUser user) { - if (mCurrentForegroundUser == null - || mCurrentForegroundUser.getUserIdentifier() != user.getUserIdentifier()) { + void notifyUserUnlocking(@NonNull SystemService.TargetUser user) { + boolean isSameAsForegroundUser = + mCurrentForegroundUser != null + && mCurrentForegroundUser.getUserIdentifier() == user.getUserIdentifier(); + if (!isSameAsForegroundUser) { return; } - mCurrentForegroundUser = null; - evaluateGameServiceConnection(); + // It is likely that the Game Service provider's components are not Direct Boot mode aware + // and will not be capable of running until the user has unlocked the device. To allow for + // this we re-evaluate the active game service provider once these components are available. + + mBackgroundExecutor.execute(this::evaluateActiveGameServiceProvider); } - private void evaluateGameServiceConnection() { - if (!mHasBootCompleted) { + void notifyUserStopped(@NonNull SystemService.TargetUser user) { + boolean isSameAsForegroundUser = + mCurrentForegroundUser != null + && mCurrentForegroundUser.getUserIdentifier() == user.getUserIdentifier(); + if (!isSameAsForegroundUser) { return; } - // TODO(b/204565942): Only shutdown the existing service connection if the game service - // provider or user has changed. - if (mGameServiceConnection != null) { - mGameServiceConnection.disconnect(); - mGameServiceConnection = null; - } + setCurrentForegroundUserAndEvaluateProvider(null); + } - boolean isUserSupported = - mCurrentForegroundUser != null - && mCurrentForegroundUser.isFull() - && !mCurrentForegroundUser.isManagedProfile(); - if (!isUserSupported) { - if (DEBUG && mCurrentForegroundUser != null) { - Slog.d(TAG, "User not supported: " + mCurrentForegroundUser); - } + private void setCurrentForegroundUserAndEvaluateProvider( + @Nullable SystemService.TargetUser user) { + boolean hasUserChanged = + !Objects.equals(mCurrentForegroundUser, user); + if (!hasUserChanged) { return; } + mCurrentForegroundUser = user; + + mBackgroundExecutor.execute(this::evaluateActiveGameServiceProvider); + } - ComponentName gameServiceComponentName = - determineGameServiceComponentName(mCurrentForegroundUser.getUserIdentifier()); - if (gameServiceComponentName == null) { + @WorkerThread + private void evaluateActiveGameServiceProvider() { + if (!mHasBootCompleted) { return; } - mGameServiceConnection = new GameServiceConnection( - mContext, - gameServiceComponentName, - mCurrentForegroundUser.getUserIdentifier()); - mGameServiceConnection.connect(); - } + synchronized (mLock) { + GameServiceProviderConfiguration selectedGameServiceProviderConfiguration = + mGameServiceProviderSelector.get(mCurrentForegroundUser); - @Nullable - private ComponentName determineGameServiceComponentName(int userId) { - String gameServicePackage = - mContext.getResources().getString( - com.android.internal.R.string.config_systemGameService); - if (TextUtils.isEmpty(gameServicePackage)) { - if (DEBUG) { - Slog.d(TAG, "No game service package defined"); + boolean didActiveGameServiceProviderChanged = + !Objects.equals(selectedGameServiceProviderConfiguration, + mActiveGameServiceProviderConfiguration); + if (!didActiveGameServiceProviderChanged) { + return; } - return null; - } - List<ResolveInfo> gameServiceResolveInfos = - mContext.getPackageManager().queryIntentServicesAsUser( - new Intent(GameService.SERVICE_INTERFACE).setPackage(gameServicePackage), - PackageManager.MATCH_SYSTEM_ONLY, - userId); + if (mGameServiceProviderInstance != null) { + Slog.i(TAG, "Stopping Game Service provider: " + + mActiveGameServiceProviderConfiguration); + mGameServiceProviderInstance.stop(); + } - if (gameServiceResolveInfos.isEmpty()) { - Slog.v(TAG, "No available game service found for user id: " + userId); - return null; - } + mActiveGameServiceProviderConfiguration = selectedGameServiceProviderConfiguration; - for (ResolveInfo resolveInfo : gameServiceResolveInfos) { - if (resolveInfo.serviceInfo == null) { - continue; - } - final ServiceInfo serviceInfo = resolveInfo.serviceInfo; - if (!serviceInfo.isEnabled()) { - continue; + if (mActiveGameServiceProviderConfiguration == null) { + return; } - return serviceInfo.getComponentName(); - } - Slog.v(TAG, "No game service found for user id: " + userId); - return null; + Slog.i(TAG, + "Starting Game Service provider: " + mActiveGameServiceProviderConfiguration); + mGameServiceProviderInstance = + mGameServiceProviderInstanceFactory.create( + mActiveGameServiceProviderConfiguration); + mGameServiceProviderInstance.start(); + } } } diff --git a/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java b/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java new file mode 100644 index 000000000000..7c8f251f35fe --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.os.UserHandle; + +import java.util.Objects; + +/** + * Representation of a {@link android.service.games.GameService} provider configuration. + */ +final class GameServiceProviderConfiguration { + private final UserHandle mUserHandle; + private final ComponentName mGameServiceComponentName; + private final ComponentName mGameSessionServiceComponentName; + + GameServiceProviderConfiguration( + @NonNull UserHandle userHandle, + @NonNull ComponentName gameServiceComponentName, + @NonNull ComponentName gameSessionServiceComponentName) { + Objects.requireNonNull(userHandle); + Objects.requireNonNull(gameServiceComponentName); + Objects.requireNonNull(gameSessionServiceComponentName); + + this.mUserHandle = userHandle; + this.mGameServiceComponentName = gameServiceComponentName; + this.mGameSessionServiceComponentName = gameSessionServiceComponentName; + } + + @NonNull + public UserHandle getUserHandle() { + return mUserHandle; + } + + @NonNull + public ComponentName getGameServiceComponentName() { + return mGameServiceComponentName; + } + + @NonNull + public ComponentName getGameSessionServiceComponentName() { + return mGameSessionServiceComponentName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameServiceProviderConfiguration)) { + return false; + } + + GameServiceProviderConfiguration that = (GameServiceProviderConfiguration) o; + return mUserHandle.equals(that.mUserHandle) + && mGameServiceComponentName.equals(that.mGameServiceComponentName) + && mGameSessionServiceComponentName.equals(that.mGameSessionServiceComponentName); + } + + @Override + public int hashCode() { + return Objects.hash(mUserHandle, mGameServiceComponentName, + mGameSessionServiceComponentName); + } + + @Override + public String toString() { + return "GameServiceProviderConfiguration{" + + "mUserHandle=" + + mUserHandle + + ", gameServiceComponentName=" + + mGameServiceComponentName + + ", gameSessionServiceComponentName=" + + mGameSessionServiceComponentName + + '}'; + } +} diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstance.java b/services/core/java/com/android/server/app/GameServiceProviderInstance.java new file mode 100644 index 000000000000..e83f9acca66e --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceProviderInstance.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 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.app; + +/** + * Representation of an instance of a Game Service provider. + * + * This includes maintaining the bindings and driving the interactions with the provider's + * implementations of {@link android.service.games.GameService} and + * {@link android.service.games.GameSessionService}. + */ +interface GameServiceProviderInstance { + /** + * Begins running the Game Service provider instance. + */ + void start(); + + /** + * Stops running the Game Service provider instance. + */ + void stop(); +} diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java new file mode 100644 index 000000000000..7640cc555446 --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; + +/** + * Factory for creating {@link GameServiceProviderInstance}. + */ +interface GameServiceProviderInstanceFactory { + + @NonNull + GameServiceProviderInstance create(@NonNull + GameServiceProviderConfiguration gameServiceProviderConfiguration); +} diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java new file mode 100644 index 000000000000..d5ac03ab7c0d --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.app.ActivityTaskManager; +import android.content.Context; +import android.content.Intent; +import android.service.games.GameService; +import android.service.games.GameSessionService; +import android.service.games.IGameService; +import android.service.games.IGameSessionService; + +import com.android.internal.infra.ServiceConnector; +import com.android.internal.os.BackgroundThread; + +final class GameServiceProviderInstanceFactoryImpl implements GameServiceProviderInstanceFactory { + private final Context mContext; + + GameServiceProviderInstanceFactoryImpl(@NonNull Context context) { + this.mContext = context; + } + + @NonNull + @Override + public GameServiceProviderInstance create(@NonNull + GameServiceProviderConfiguration gameServiceProviderConfiguration) { + return new GameServiceProviderInstanceImpl( + gameServiceProviderConfiguration.getUserHandle(), + BackgroundThread.getExecutor(), + new GameClassifierImpl(mContext.getPackageManager()), + ActivityTaskManager.getService(), + new GameServiceConnector(mContext, gameServiceProviderConfiguration), + new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration)); + } + + private static final class GameServiceConnector extends ServiceConnector.Impl<IGameService> { + private static final int DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT = 0; + private static final int BINDING_FLAGS = Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS; + + GameServiceConnector( + @NonNull Context context, + @NonNull GameServiceProviderConfiguration configuration) { + super(context, new Intent(GameService.ACTION_GAME_SERVICE) + .setComponent(configuration.getGameServiceComponentName()), + BINDING_FLAGS, configuration.getUserHandle().getIdentifier(), + IGameService.Stub::asInterface); + } + + @Override + protected long getAutoDisconnectTimeoutMs() { + return DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT; + } + } + + private static final class GameSessionServiceConnector extends + ServiceConnector.Impl<IGameSessionService> { + private static final int DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT = 0; + private static final int BINDING_FLAGS = + Context.BIND_TREAT_LIKE_ACTIVITY + | Context.BIND_SCHEDULE_LIKE_TOP_APP + | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS; + + GameSessionServiceConnector( + @NonNull Context context, + @NonNull GameServiceProviderConfiguration configuration) { + super(context, new Intent(GameSessionService.ACTION_GAME_SESSION_SERVICE) + .setComponent(configuration.getGameSessionServiceComponentName()), + BINDING_FLAGS, configuration.getUserHandle().getIdentifier(), + IGameSessionService.Stub::asInterface); + } + + @Override + protected long getAutoDisconnectTimeoutMs() { + return DISABLE_AUTOMATIC_DISCONNECT_TIMEOUT; + } + } +} diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java new file mode 100644 index 000000000000..3f3f257aedc5 --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; +import android.content.ComponentName; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.service.games.CreateGameSessionRequest; +import android.service.games.IGameService; +import android.service.games.IGameSession; +import android.service.games.IGameSessionService; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.AndroidFuture; +import com.android.internal.infra.ServiceConnector; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +final class GameServiceProviderInstanceImpl implements GameServiceProviderInstance { + private static final String TAG = "GameServiceProviderInstance"; + private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000; + private static final boolean DEBUG = false; + + private final TaskStackListener mTaskStackListener = new TaskStackListener() { + @Override + public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { + if (componentName == null) { + return; + } + + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.onTaskCreated(taskId, componentName); + }); + } + + @Override + public void onTaskRemoved(int taskId) throws RemoteException { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.onTaskRemoved(taskId); + }); + } + }; + private final Object mLock = new Object(); + private final UserHandle mUserHandle; + private final Executor mBackgroundExecutor; + private final GameClassifier mGameClassifier; + private final IActivityTaskManager mActivityTaskManager; + private final ServiceConnector<IGameService> mGameServiceConnector; + private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector; + + @GuardedBy("mLock") + private final ConcurrentHashMap<Integer, GameSessionRecord> mGameSessions = + new ConcurrentHashMap<>(); + @GuardedBy("mLock") + private volatile boolean mIsRunning; + + GameServiceProviderInstanceImpl( + UserHandle userHandle, + @NonNull Executor backgroundExecutor, + @NonNull GameClassifier gameClassifier, + @NonNull IActivityTaskManager activityTaskManager, + @NonNull ServiceConnector<IGameService> gameServiceConnector, + @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) { + mUserHandle = userHandle; + mBackgroundExecutor = backgroundExecutor; + mGameClassifier = gameClassifier; + mActivityTaskManager = activityTaskManager; + mGameServiceConnector = gameServiceConnector; + mGameSessionServiceConnector = gameSessionServiceConnector; + } + + @Override + public void start() { + synchronized (mLock) { + startLocked(); + } + } + + @Override + public void stop() { + synchronized (mLock) { + stopLocked(); + } + } + + @GuardedBy("mLock") + private void startLocked() { + if (mIsRunning) { + return; + } + mIsRunning = true; + + // TODO(b/204503192): In cases where the connection to the game service fails retry with + // back off mechanism. + AndroidFuture<Void> unusedPostConnectedFuture = mGameServiceConnector.post(gameService -> { + gameService.connected(); + }); + + try { + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to register task stack listener", e); + } + } + + @GuardedBy("mLock") + private void stopLocked() { + if (!mIsRunning) { + return; + } + mIsRunning = false; + + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to unregister task stack listener", e); + } + + for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { + IGameSession gameSession = gameSessionRecord.getGameSession(); + if (gameSession == null) { + continue; + } + + try { + gameSession.destroy(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); + } + } + mGameSessions.clear(); + + // TODO(b/204503192): It is possible that the game service is disconnected. In this + // case we should avoid rebinding just to shut it down again. + AndroidFuture<Void> unusedPostDisconnectedFuture = + mGameServiceConnector.post(gameService -> { + gameService.disconnected(); + }); + mGameServiceConnector.unbind(); + mGameSessionServiceConnector.unbind(); + } + + private void onTaskCreated(int taskId, @NonNull ComponentName componentName) { + String packageName = componentName.getPackageName(); + if (!mGameClassifier.isGame(packageName, mUserHandle)) { + return; + } + + synchronized (mLock) { + createGameSessionLocked(taskId, componentName); + } + } + + private void onTaskRemoved(int taskId) { + synchronized (mLock) { + boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId); + if (!isTaskAssociatedWithGameSession) { + return; + } + + destroyGameSessionLocked(taskId); + } + } + + @GuardedBy("mLock") + private void createGameSessionLocked(int sessionId, @NonNull ComponentName componentName) { + if (DEBUG) { + Slog.i(TAG, "createGameSession() id: " + sessionId + " component: " + componentName); + } + + if (!mIsRunning) { + return; + } + + GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId); + if (existingGameSessionRecord != null) { + Slog.w(TAG, "Existing game session found for task (id: " + sessionId + + ") creation. Ignoring."); + return; + } + + GameSessionRecord gameSessionRecord = GameSessionRecord.pendingGameSession(sessionId, + componentName); + mGameSessions.put(sessionId, gameSessionRecord); + + // TODO(b/207035150): Allow the game service provider to determine if a game session + // should be created. For now we will assume all games should have a session. + AndroidFuture<IBinder> gameSessionFuture = new AndroidFuture<IBinder>() + .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .whenCompleteAsync((gameSessionIBinder, exception) -> { + IGameSession gameSession = IGameSession.Stub.asInterface(gameSessionIBinder); + if (exception != null || gameSession == null) { + Slog.w(TAG, "Failed to create GameSession: " + gameSessionRecord, + exception); + synchronized (mLock) { + destroyGameSessionLocked(sessionId); + } + return; + } + + synchronized (mLock) { + attachGameSessionLocked(sessionId, gameSession); + } + }, mBackgroundExecutor); + + AndroidFuture<Void> unusedPostCreateGameSessionFuture = + mGameSessionServiceConnector.post(gameService -> { + CreateGameSessionRequest createGameSessionRequest = + new CreateGameSessionRequest(sessionId, componentName.getPackageName()); + gameService.create(createGameSessionRequest, gameSessionFuture); + }); + } + + @GuardedBy("mLock") + private void attachGameSessionLocked(int sessionId, @NonNull IGameSession gameSession) { + if (DEBUG) { + Slog.i(TAG, "attachGameSession() id: " + sessionId); + } + + GameSessionRecord gameSessionRecord = mGameSessions.get(sessionId); + if (gameSessionRecord == null) { + Slog.w(TAG, "No associated game session record. Destroying id: " + sessionId); + + try { + gameSession.destroy(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); + } + return; + } + + mGameSessions.put(sessionId, gameSessionRecord.withGameSession(gameSession)); + } + + @GuardedBy("mLock") + private void destroyGameSessionLocked(int sessionId) { + // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider + // to only when the associated task is running. Right now it is possible for a task to + // move into the background and for all associated processes to die and for the Game Session + // provider's GameSessionService to continue to be running. Ideally we could unbind the + // service when this happens. + if (DEBUG) { + Slog.i(TAG, "destroyGameSession() id: " + sessionId); + } + + GameSessionRecord gameSessionRecord = mGameSessions.remove(sessionId); + if (gameSessionRecord == null) { + if (DEBUG) { + Slog.w(TAG, "No game session found for id: " + sessionId); + } + return; + } + + IGameSession gameSession = gameSessionRecord.getGameSession(); + if (gameSession != null) { + try { + gameSession.destroy(); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); + } + } + + if (mGameSessions.isEmpty()) { + if (DEBUG) { + Slog.i(TAG, "No active game sessions. Disconnecting GameSessionService"); + } + + if (mGameSessionServiceConnector != null) { + mGameSessionServiceConnector.unbind(); + } + } + } +} diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelector.java b/services/core/java/com/android/server/app/GameServiceProviderSelector.java new file mode 100644 index 000000000000..51d35157b332 --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceProviderSelector.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.Nullable; + +import com.android.server.SystemService; + +/** + * Responsible for determining what the active Game Service provider should be. + */ +interface GameServiceProviderSelector { + + /** + * Returns the {@link GameServiceProviderConfiguration} associated with the selected Game + * Service provider for the given user or {@code null} if none should be used. + */ + @Nullable + GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user); +} diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java new file mode 100644 index 000000000000..54ef70715b86 --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.UserHandle; +import android.service.games.GameService; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Slog; +import android.util.Xml; + +import com.android.server.SystemService; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.List; + +final class GameServiceProviderSelectorImpl implements GameServiceProviderSelector { + private static final String TAG = "GameServiceProviderSelector"; + private static final String GAME_SERVICE_NODE_NAME = "game-service"; + private static final boolean DEBUG = false; + + private final Resources mResources; + private final PackageManager mPackageManager; + + GameServiceProviderSelectorImpl(@NonNull Resources resources, + @NonNull PackageManager packageManager) { + mResources = resources; + mPackageManager = packageManager; + } + + @Override + @Nullable + public GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user) { + if (user == null) { + return null; + } + + boolean isUserSupported = user.isFull() && !user.isManagedProfile(); + if (!isUserSupported) { + Slog.i(TAG, "Game Service not supported for user: " + user.getUserIdentifier()); + return null; + } + + String gameServicePackage = + mResources.getString( + com.android.internal.R.string.config_systemGameService); + + if (TextUtils.isEmpty(gameServicePackage)) { + Slog.w(TAG, "No game service package defined"); + return null; + } + + int userId = user.getUserIdentifier(); + List<ResolveInfo> gameServiceResolveInfos = + mPackageManager.queryIntentServicesAsUser( + new Intent(GameService.ACTION_GAME_SERVICE).setPackage(gameServicePackage), + PackageManager.GET_META_DATA | PackageManager.MATCH_SYSTEM_ONLY, + userId); + if (DEBUG) { + Slog.i(TAG, "Querying package: " + gameServicePackage + " and user id: " + userId); + Slog.i(TAG, "Found resolve infos: " + gameServiceResolveInfos); + } + + if (gameServiceResolveInfos == null || gameServiceResolveInfos.isEmpty()) { + Slog.w(TAG, "No available game service found for user id: " + userId); + return null; + } + + GameServiceProviderConfiguration selectedProvider = null; + for (ResolveInfo resolveInfo : gameServiceResolveInfos) { + if (resolveInfo.serviceInfo == null) { + continue; + } + ServiceInfo gameServiceServiceInfo = resolveInfo.serviceInfo; + + ComponentName gameSessionServiceComponentName = + determineGameSessionServiceFromGameService(gameServiceServiceInfo); + if (gameSessionServiceComponentName == null) { + continue; + } + + selectedProvider = + new GameServiceProviderConfiguration( + new UserHandle(userId), + gameServiceServiceInfo.getComponentName(), + gameSessionServiceComponentName); + break; + } + + if (selectedProvider == null) { + Slog.w(TAG, "No valid game service found for user id: " + userId); + return null; + } + + return selectedProvider; + } + + @Nullable + private ComponentName determineGameSessionServiceFromGameService( + @NonNull ServiceInfo gameServiceServiceInfo) { + String gameSessionService; + try (XmlResourceParser parser = gameServiceServiceInfo.loadXmlMetaData(mPackageManager, + GameService.SERVICE_META_DATA)) { + if (parser == null) { + Slog.w(TAG, "No " + GameService.SERVICE_META_DATA + " meta-data found for " + + gameServiceServiceInfo.getComponentName()); + return null; + } + + Resources resources = mPackageManager.getResourcesForApplication( + gameServiceServiceInfo.packageName); + + AttributeSet attributeSet = Xml.asAttributeSet(parser); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // Do nothing + } + + boolean isStartingTagGameService = GAME_SERVICE_NODE_NAME.equals(parser.getName()); + if (!isStartingTagGameService) { + Slog.w(TAG, "Meta-data does not start with " + GAME_SERVICE_NODE_NAME + " tag"); + return null; + } + + TypedArray array = resources.obtainAttributes(attributeSet, + com.android.internal.R.styleable.GameService); + gameSessionService = array.getString( + com.android.internal.R.styleable.GameService_gameSessionService); + array.recycle(); + } catch (PackageManager.NameNotFoundException | XmlPullParserException | IOException ex) { + Slog.w("Error while parsing meta-data for " + gameServiceServiceInfo.getComponentName(), + ex); + return null; + } + + if (TextUtils.isEmpty(gameSessionService)) { + Slog.w(TAG, "No gameSessionService specified"); + return null; + } + ComponentName componentName = + new ComponentName(gameServiceServiceInfo.packageName, gameSessionService); + + try { + mPackageManager.getServiceInfo(componentName, /* flags= */ 0); + } catch (PackageManager.NameNotFoundException ex) { + Slog.w(TAG, "GameSessionService does not exist: " + componentName); + return null; + } + + return componentName; + } +} diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java new file mode 100644 index 000000000000..329e9e8144e0 --- /dev/null +++ b/services/core/java/com/android/server/app/GameSessionRecord.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.service.games.IGameSession; + +import java.util.Objects; + +final class GameSessionRecord { + + private final int mTaskId; + private final ComponentName mRootComponentName; + @Nullable + private final IGameSession mIGameSession; + + static GameSessionRecord pendingGameSession(int taskId, ComponentName rootComponentName) { + return new GameSessionRecord(taskId, rootComponentName, /* gameSession= */ null); + } + + private GameSessionRecord( + int taskId, + @NonNull ComponentName rootComponentName, + @Nullable IGameSession gameSession) { + this.mTaskId = taskId; + this.mRootComponentName = rootComponentName; + this.mIGameSession = gameSession; + } + + @NonNull + public GameSessionRecord withGameSession(@NonNull IGameSession gameSession) { + Objects.requireNonNull(gameSession); + return new GameSessionRecord(mTaskId, mRootComponentName, gameSession); + } + + @Nullable + public IGameSession getGameSession() { + return mIGameSession; + } + + @Override + public String toString() { + return "GameSessionRecord{" + + "mTaskId=" + + mTaskId + + ", mRootComponentName=" + + mRootComponentName + + ", mIGameSession=" + + mIGameSession + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameSessionRecord)) { + return false; + } + + GameSessionRecord that = (GameSessionRecord) o; + return mTaskId == that.mTaskId && mRootComponentName.equals(that.mRootComponentName) + && Objects.equals(mIGameSession, that.mIGameSession); + } + + @Override + public int hashCode() { + return Objects.hash(mTaskId, mRootComponentName, mIGameSession); + } +} diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index b333ed24eb1d..406b2dd2486d 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -817,7 +817,7 @@ public final class PlaybackActivityMonitor // the same time if we still have a public client. while (clientIterator.hasNext()) { PlayMonitorClient pmc = clientIterator.next(); - if (pcdb.equals(pmc.mDispatcherCb)) { + if (pcdb.asBinder().equals(pmc.mDispatcherCb.asBinder())) { pmc.release(); clientIterator.remove(); } else { diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index e2e56ae8e620..9dd7daf1a1cc 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -36,6 +36,8 @@ import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.CompatibilityOverridesByPackageConfig; +import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig; import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.OverrideAllowedState; @@ -220,15 +222,47 @@ final class CompatConfig { } /** - * Overrides the enabled state for a given change and app. + * Adds compat config overrides for multiple packages. + * + * <p>Equivalent to calling + * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} on each entry + * in {@code overridesByPackage}, but the state of the compat config will be updated only + * once instead of for each package. + * + * @param overridesByPackage map from package name to compat config overrides to add for that + * package. + * @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overridesByPackage}. + */ + synchronized void addAllPackageOverrides( + CompatibilityOverridesByPackageConfig overridesByPackage, + boolean skipUnknownChangeIds) { + for (String packageName : overridesByPackage.packageNameToOverrides.keySet()) { + addPackageOverridesWithoutSaving( + overridesByPackage.packageNameToOverrides.get(packageName), packageName, + skipUnknownChangeIds); + } + saveOverrides(); + invalidateCache(); + } + + /** + * Adds compat config overrides for a given package. * + * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. * - * @param overrides list of overrides to default changes config. - * @param packageName app for which the overrides will be applied. + * @param overrides list of compat config overrides to add for the given package. + * @param packageName app for which the overrides will be applied. * @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overrides}. */ synchronized void addPackageOverrides(CompatibilityOverrideConfig overrides, String packageName, boolean skipUnknownChangeIds) { + addPackageOverridesWithoutSaving(overrides, packageName, skipUnknownChangeIds); + saveOverrides(); + invalidateCache(); + } + + private void addPackageOverridesWithoutSaving(CompatibilityOverrideConfig overrides, + String packageName, boolean skipUnknownChangeIds) { for (Long changeId : overrides.overrides.keySet()) { if (skipUnknownChangeIds && !isKnownChangeId(changeId)) { Slog.w(TAG, "Trying to add overrides for unknown Change ID " + changeId + ". " @@ -237,8 +271,6 @@ final class CompatConfig { } addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId)); } - saveOverrides(); - invalidateCache(); } private boolean addOverrideUnsafe(long changeId, String packageName, @@ -344,6 +376,36 @@ final class CompatConfig { } /** + * Removes overrides with a specified change ID that were previously added via + * {@link #addOverride(long, String, boolean)} or + * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for multiple + * packages. + * + * <p>Equivalent to calling + * {@link #removePackageOverrides(CompatibilityOverridesToRemoveConfig, String)} on each entry + * in {@code overridesToRemoveByPackage}, but the state of the compat config will be updated + * only once instead of for each package. + * + * @param overridesToRemoveByPackage map from package name to a list of change IDs for + * which to restore the default behaviour for that + * package. + */ + synchronized void removeAllPackageOverrides( + CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) { + boolean shouldInvalidateCache = false; + for (String packageName : + overridesToRemoveByPackage.packageNameToOverridesToRemove.keySet()) { + shouldInvalidateCache |= removePackageOverridesWithoutSaving( + overridesToRemoveByPackage.packageNameToOverridesToRemove.get(packageName), + packageName); + } + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); + } + } + + /** * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain * package. @@ -377,6 +439,16 @@ final class CompatConfig { */ synchronized void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) { + boolean shouldInvalidateCache = removePackageOverridesWithoutSaving(overridesToRemove, + packageName); + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); + } + } + + private boolean removePackageOverridesWithoutSaving( + CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) { boolean shouldInvalidateCache = false; for (Long changeId : overridesToRemove.changeIds) { if (!isKnownChangeId(changeId)) { @@ -386,10 +458,7 @@ final class CompatConfig { } shouldInvalidateCache |= removeOverrideUnsafe(changeId, packageName); } - if (shouldInvalidateCache) { - saveOverrides(); - invalidateCache(); - } + return shouldInvalidateCache; } private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName, diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 6ea89d445402..aab6281e6cd1 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -47,6 +47,8 @@ import com.android.internal.compat.ChangeReporter; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.CompatibilityOverridesByPackageConfig; +import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig; import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.IPlatformCompat; @@ -226,9 +228,19 @@ public class PlatformCompat extends IPlatformCompat.Stub { } @Override + public void putAllOverridesOnReleaseBuilds( + CompatibilityOverridesByPackageConfig overridesByPackage) { + checkCompatChangeOverrideOverridablePermission(); + for (CompatibilityOverrideConfig overrides : + overridesByPackage.packageNameToOverrides.values()) { + checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); + } + mCompatConfig.addAllPackageOverrides(overridesByPackage, /* skipUnknownChangeIds= */ true); + } + + @Override public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, String packageName) { - // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods. checkCompatChangeOverrideOverridablePermission(); checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true); @@ -280,10 +292,20 @@ public class PlatformCompat extends IPlatformCompat.Stub { } @Override + public void removeAllOverridesOnReleaseBuilds( + CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) { + checkCompatChangeOverrideOverridablePermission(); + for (CompatibilityOverridesToRemoveConfig overridesToRemove : + overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) { + checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); + } + mCompatConfig.removeAllPackageOverrides(overridesToRemoveByPackage); + } + + @Override public void removeOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) { - // TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods. checkCompatChangeOverrideOverridablePermission(); checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); mCompatConfig.removePackageOverrides(overridesToRemove, packageName); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index bf4ef4879c9a..a2fed291cb38 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -17,6 +17,8 @@ package com.android.server.connectivity; import static android.Manifest.permission.BIND_VPN_SERVICE; +import static android.Manifest.permission.CONTROL_VPN; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; @@ -932,6 +934,7 @@ public class Vpn { * - oldPackage null, newPackage non-null: ConfirmDialog calling prepareVpn(). * - oldPackage null, newPackage=LEGACY_VPN: Used internally to disconnect * and revoke any current app VPN and re-prepare legacy vpn. + * - oldPackage null, newPackage null: always returns true for backward compatibility. * * TODO: Rename the variables - or split this method into two - and end this confusion. * TODO: b/29032008 Migrate code from prepare(oldPackage=non-null, newPackage=LEGACY_VPN) @@ -945,6 +948,18 @@ public class Vpn { */ public synchronized boolean prepare( String oldPackage, String newPackage, @VpnManager.VpnType int vpnType) { + // Except for Settings and VpnDialogs, the caller should be matched one of oldPackage or + // newPackage. Otherwise, non VPN owner might get the VPN always-on status of the VPN owner. + // See b/191382886. + if (mContext.checkCallingOrSelfPermission(CONTROL_VPN) != PERMISSION_GRANTED) { + if (oldPackage != null) { + verifyCallingUidAndPackage(oldPackage); + } + if (newPackage != null) { + verifyCallingUidAndPackage(newPackage); + } + } + if (oldPackage != null) { // Stop an existing always-on VPN from being dethroned by other apps. if (mAlwaysOn && !isCurrentPreparedPackage(oldPackage)) { @@ -1859,14 +1874,13 @@ public class Vpn { } private void enforceControlPermission() { - mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller"); + mContext.enforceCallingPermission(CONTROL_VPN, "Unauthorized Caller"); } private void enforceControlPermissionOrInternalCaller() { // Require the caller to be either an application with CONTROL_VPN permission or a process // in the system server. - mContext.enforceCallingOrSelfPermission(Manifest.permission.CONTROL_VPN, - "Unauthorized Caller"); + mContext.enforceCallingOrSelfPermission(CONTROL_VPN, "Unauthorized Caller"); } private void enforceSettingsPermission() { @@ -3176,8 +3190,9 @@ public class Vpn { } private void verifyCallingUidAndPackage(String packageName) { - if (getAppUid(packageName, mUserId) != Binder.getCallingUid()) { - throw new SecurityException("Mismatched package and UID"); + final int callingUid = Binder.getCallingUid(); + if (getAppUid(packageName, mUserId) != callingUid) { + throw new SecurityException(packageName + " does not belong to uid " + callingUid); } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 3d04037185f3..261aa32f093e 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -347,6 +347,7 @@ public class InputManagerService extends IInputManager.Stub private static native boolean nativeEnableSensor(long ptr, int deviceId, int sensorType, int samplingPeriodUs, int maxBatchReportLatencyUs); private static native void nativeDisableSensor(long ptr, int deviceId, int sensorType); + private static native void nativeCancelCurrentTouch(long ptr); // Maximum number of milliseconds to wait for input event injection. private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; @@ -2510,6 +2511,16 @@ public class InputManagerService extends IInputManager.Stub } @Override + public void cancelCurrentTouch() { + if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT, + "cancelCurrentTouch()")) { + throw new SecurityException("Requires MONITOR_INPUT permission"); + } + + nativeCancelCurrentTouch(mPtr); + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 9078f3ffeb54..cc5aaf4f7f45 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -41,8 +41,10 @@ import android.util.Pair; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; @@ -320,9 +322,8 @@ public abstract class IContextHubWrapper { private static class ContextHubWrapperAidl extends IContextHubWrapper { private android.hardware.contexthub.IContextHub mHub; - private ICallback mCallback = null; - - private ContextHubAidlCallback mAidlCallback = new ContextHubAidlCallback(); + private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap = + new HashMap<>(); // Use this thread in case where the execution requires to be on a service thread. // For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission. @@ -332,6 +333,14 @@ public abstract class IContextHubWrapper { private class ContextHubAidlCallback extends android.hardware.contexthub.IContextHubCallback.Stub { + private final int mContextHubId; + private final ICallback mCallback; + + ContextHubAidlCallback(int contextHubId, ICallback callback) { + mContextHubId = contextHubId; + mCallback = callback; + } + public void handleNanoappInfo(android.hardware.contexthub.NanoappInfo[] appInfo) { List<NanoAppState> nanoAppStateList = ContextHubServiceUtil.createNanoAppStateList(appInfo); @@ -481,8 +490,8 @@ public abstract class IContextHubWrapper { } public void registerCallback(int contextHubId, ICallback callback) throws RemoteException { - mCallback = callback; - mHub.registerCallback(contextHubId, mAidlCallback); + mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback)); + mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId)); } @ContextHubTransaction.Result @@ -508,10 +517,18 @@ public abstract class IContextHubWrapper { protected ICallback mCallback = null; - protected final ContextHubWrapperHidlCallback mHidlCallback = - new ContextHubWrapperHidlCallback(); + protected final Map<Integer, ContextHubWrapperHidlCallback> mHidlCallbackMap = + new HashMap<>(); protected class ContextHubWrapperHidlCallback extends IContexthubCallback.Stub { + private final int mContextHubId; + private final ICallback mCallback; + + ContextHubWrapperHidlCallback(int contextHubId, ICallback callback) { + mContextHubId = contextHubId; + mCallback = callback; + } + @Override public void handleClientMsg(ContextHubMsg message) { mCallback.handleNanoappMessage( @@ -612,8 +629,9 @@ public abstract class IContextHubWrapper { } public void registerCallback(int contextHubId, ICallback callback) throws RemoteException { - mCallback = callback; - mHub.registerCallback(contextHubId, mHidlCallback); + mHidlCallbackMap.put(contextHubId, + new ContextHubWrapperHidlCallback(contextHubId, callback)); + mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId)); } public void onWifiMainSettingChanged(boolean enabled) {} @@ -779,8 +797,9 @@ public abstract class IContextHubWrapper { } public void registerCallback(int contextHubId, ICallback callback) throws RemoteException { - mCallback = callback; - mHub.registerCallback_1_2(contextHubId, mHidlCallback); + mHidlCallbackMap.put(contextHubId, + new ContextHubWrapperHidlCallback(contextHubId, callback)); + mHub.registerCallback_1_2(contextHubId, mHidlCallbackMap.get(contextHubId)); } private void sendSettingChanged(byte setting, byte newValue) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index bf50db85d984..a860d35b83dc 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -2436,7 +2436,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { networkId, templateMeteredness, NetworkStats.ROAMING_ALL, NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL, NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule); - if (template.isPersistable()) { + if (NetworkPolicy.isTemplatePersistable(template)) { mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule, warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred)); @@ -2643,7 +2643,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int i = 0; i < mNetworkPolicy.size(); i++) { final NetworkPolicy policy = mNetworkPolicy.valueAt(i); final NetworkTemplate template = policy.template; - if (!template.isPersistable()) continue; + if (!NetworkPolicy.isTemplatePersistable(template)) continue; out.startTag(null, TAG_NETWORK_POLICY); writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule()); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 137fc85c1e72..20686322fd3d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7871,7 +7871,9 @@ public class NotificationManagerService extends SystemService { int index = mToastQueue.indexOf(record); if (index >= 0) { - mToastQueue.remove(index); + ToastRecord toast = mToastQueue.remove(index); + mWindowManagerInternal.removeWindowToken( + toast.windowToken, true /* removeWindows */, toast.displayId); } record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null; } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 1945ed054e21..2128beaa7322 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -146,7 +146,6 @@ import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationUtils; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.utils.WatchedArrayMap; -import com.android.server.utils.WatchedArraySet; import com.android.server.utils.WatchedLongSparseArray; import com.android.server.utils.WatchedSparseBooleanArray; import com.android.server.utils.WatchedSparseIntArray; @@ -333,7 +332,7 @@ public class ComputerEngine implements Computer { private final InstantAppRegistry mInstantAppRegistry; private final ApplicationInfo mLocalAndroidApplication; private final AppsFilter mAppsFilter; - private final WatchedArraySet<String> mFrozenPackages; + private final WatchedArrayMap<String, Integer> mFrozenPackages; // Immutable service attribute private final String mAppPredictionServicePackage; @@ -3580,7 +3579,7 @@ public class ComputerEngine implements Computer { return PackageManagerService.PACKAGE_STARTABILITY_NOT_SYSTEM; } - if (mFrozenPackages.contains(packageName)) { + if (mFrozenPackages.containsKey(packageName)) { return PackageManagerService.PACKAGE_STARTABILITY_FROZEN; } diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java index c670e1fe69ff..47e94f37ec8c 100644 --- a/services/core/java/com/android/server/pm/DumpHelper.java +++ b/services/core/java/com/android/server/pm/DumpHelper.java @@ -504,6 +504,9 @@ final class DumpHelper { ipw.println("(none)"); } else { for (int i = 0; i < mPm.mFrozenPackages.size(); i++) { + ipw.print("package="); + ipw.print(mPm.mFrozenPackages.keyAt(i)); + ipw.print(", refCounts="); ipw.println(mPm.mFrozenPackages.valueAt(i)); } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 8e6746dca7fc..8573585c3c6b 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2992,7 +2992,8 @@ final class InstallPackageHelper { installPackageFromSystemLIF(stubPkg.getPath(), mPm.mUserManager.getUserIds() /*allUserHandles*/, null /*origUserHandles*/, - true /*writeSettings*/); + true /*writeSettings*/, + Process.INVALID_UID /*previousAppId*/); } catch (PackageManagerException pme) { // Serious WTF; we have to be able to install the stub Slog.wtf(TAG, "Failed to restore system package:" + stubPkg.getPackageName(), @@ -3118,8 +3119,11 @@ final class InstallPackageHelper { if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); try { synchronized (mPm.mInstallLock) { + final int[] origUsers = outInfo == null ? null : outInfo.mOrigUsers; + final int previousAppId = disabledPs.getAppId() != deletedPs.getAppId() + ? deletedPs.getAppId() : Process.INVALID_UID; installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles, - outInfo == null ? null : outInfo.mOrigUsers, writeSettings); + origUsers, writeSettings, previousAppId); } } catch (PackageManagerException e) { Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": " @@ -3160,7 +3164,8 @@ final class InstallPackageHelper { */ @GuardedBy({"mPm.mLock", "mPm.mInstallLock"}) private void installPackageFromSystemLIF(@NonNull String codePathString, - @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings) + @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, + boolean writeSettings, int previousAppId) throws PackageManagerException { final File codePath = new File(codePathString); @ParsingPackageUtils.ParseFlags int parseFlags = @@ -3184,11 +3189,12 @@ final class InstallPackageHelper { mAppDataHelper.prepareAppDataAfterInstallLIF(pkg); setPackageInstalledForSystemPackage(pkg, allUserHandles, - origUserHandles, writeSettings); + origUserHandles, writeSettings, previousAppId); } private void setPackageInstalledForSystemPackage(@NonNull AndroidPackage pkg, - @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, boolean writeSettings) { + @NonNull int[] allUserHandles, @Nullable int[] origUserHandles, + boolean writeSettings, int previousAppId) { // writer synchronized (mPm.mLock) { PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName()); @@ -3222,8 +3228,7 @@ final class InstallPackageHelper { // The method below will take care of removing obsolete permissions and granting // install permissions. - mPm.mPermissionManager.onPackageInstalled(pkg, - Process.INVALID_UID /* previousAppId */, + mPm.mPermissionManager.onPackageInstalled(pkg, previousAppId, PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT, UserHandle.USER_ALL); for (final int userId : allUserHandles) { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 6e6773feb5f8..0777cdece8e7 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1685,14 +1685,12 @@ public class LauncherAppsService extends SystemService { continue; } final String[] filteredPackagesWithoutExtras = - getFilteredPackageNames(packages, cookie); - // If all packages are filtered, skip notifying listener. - if (ArrayUtils.isEmpty(filteredPackagesWithoutExtras)) { - continue; - } + getFilteredPackageNames(packagesNullExtras, cookie); try { - listener.onPackagesSuspended(user, filteredPackagesWithoutExtras, - /* launcherExtras= */ null); + if (!ArrayUtils.isEmpty(filteredPackagesWithoutExtras)) { + listener.onPackagesSuspended(user, filteredPackagesWithoutExtras, + /* launcherExtras= */ null); + } for (int idx = 0; idx < packagesWithExtras.size(); idx++) { Pair<String, Bundle> packageExtraPair = packagesWithExtras.get(idx); if (!isPackageVisibleToListener(packageExtraPair.first, cookie)) { diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index 19ebb5d1caa2..652a9ae06124 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -130,7 +130,7 @@ public final class MovePackageHelper { "Device admin cannot be moved"); } - if (mPm.mFrozenPackages.contains(packageName)) { + if (mPm.mFrozenPackages.containsKey(packageName)) { throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING, "Failed to move already frozen package"); } @@ -188,6 +188,7 @@ public final class MovePackageHelper { for (int userId : installedUserIds) { if (StorageManager.isFileEncryptedNativeOrEmulated() && !StorageManager.isUserKeyUnlocked(userId)) { + freezer.close(); throw new PackageManagerException(MOVE_FAILED_LOCKED_USER, "User " + userId + " must be unlocked"); } @@ -230,6 +231,7 @@ public final class MovePackageHelper { final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() { @Override public void onUserActionRequired(Intent intent) throws RemoteException { + freezer.close(); throw new IllegalStateException(); } diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java index ecc92b7fa740..1e0a1f2ccf1f 100644 --- a/services/core/java/com/android/server/pm/PackageFreezer.java +++ b/services/core/java/com/android/server/pm/PackageFreezer.java @@ -31,8 +31,6 @@ import java.util.concurrent.atomic.AtomicBoolean; final class PackageFreezer implements AutoCloseable { private final String mPackageName; - private final boolean mWeFroze; - private final AtomicBoolean mClosed = new AtomicBoolean(); private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -48,7 +46,7 @@ final class PackageFreezer implements AutoCloseable { PackageFreezer(PackageManagerService pm) { mPm = pm; mPackageName = null; - mWeFroze = false; + mClosed.set(true); mCloseGuard.open("close"); } @@ -58,7 +56,9 @@ final class PackageFreezer implements AutoCloseable { mPackageName = packageName; final PackageSetting ps; synchronized (mPm.mLock) { - mWeFroze = mPm.mFrozenPackages.add(mPackageName); + final int refCounts = mPm.mFrozenPackages + .getOrDefault(mPackageName, 0 /* defaultValue */) + 1; + mPm.mFrozenPackages.put(mPackageName, refCounts); ps = mPm.mSettings.getPackageLPr(mPackageName); } if (ps != null) { @@ -82,7 +82,11 @@ final class PackageFreezer implements AutoCloseable { mCloseGuard.close(); if (mClosed.compareAndSet(false, true)) { synchronized (mPm.mLock) { - if (mWeFroze) { + final int refCounts = mPm.mFrozenPackages + .getOrDefault(mPackageName, 0 /* defaultValue */) - 1; + if (refCounts > 0) { + mPm.mFrozenPackages.put(mPackageName, refCounts); + } else { mPm.mFrozenPackages.remove(mPackageName); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6f8703bf5a5d..c6ec59a5811f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -249,7 +249,6 @@ import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.utils.Watchable; import com.android.server.utils.Watched; import com.android.server.utils.WatchedArrayMap; -import com.android.server.utils.WatchedArraySet; import com.android.server.utils.WatchedLongSparseArray; import com.android.server.utils.WatchedSparseBooleanArray; import com.android.server.utils.WatchedSparseIntArray; @@ -646,15 +645,16 @@ public class PackageManagerService extends IPackageManager.Stub final Settings mSettings; /** - * Set of package names that are currently "frozen", which means active - * surgery is being done on the code/data for that package. The platform - * will refuse to launch frozen packages to avoid race conditions. + * Map of package names to frozen counts that are currently "frozen", + * which means active surgery is being done on the code/data for that + * package. The platform will refuse to launch frozen packages to avoid + * race conditions. * * @see PackageFreezer */ @GuardedBy("mLock") - final WatchedArraySet<String> mFrozenPackages = new WatchedArraySet<>(); - private final SnapshotCache<WatchedArraySet<String>> mFrozenPackagesSnapshot = + final WatchedArrayMap<String, Integer> mFrozenPackages = new WatchedArrayMap<>(); + private final SnapshotCache<WatchedArrayMap<String, Integer>> mFrozenPackagesSnapshot = new SnapshotCache.Auto(mFrozenPackages, mFrozenPackages, "PackageManagerService.mFrozenPackages"); @@ -1016,7 +1016,7 @@ public class PackageManagerService extends IPackageManager.Stub public final AppsFilter appsFilter; public final ComponentResolver componentResolver; public final PackageManagerService service; - public final WatchedArraySet<String> frozenPackages; + public final WatchedArrayMap<String, Integer> frozenPackages; Snapshot(int type) { if (type == Snapshot.SNAPPED) { @@ -7255,7 +7255,7 @@ public class PackageManagerService extends IPackageManager.Stub */ void checkPackageFrozen(String packageName) { synchronized (mLock) { - if (!mFrozenPackages.contains(packageName)) { + if (!mFrozenPackages.containsKey(packageName)) { Slog.wtf(TAG, "Expected " + packageName + " to be frozen!", new Throwable()); } } diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 9f3d19005de6..19f180f1ce4c 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -125,16 +125,10 @@ public final class SELinuxMMAC { } // Vendor mac permissions. - // The filename has been renamed from nonplat_mac_permissions to - // vendor_mac_permissions. Either of them should exist. final File vendorMacPermission = new File( Environment.getVendorDirectory(), "/etc/selinux/vendor_mac_permissions.xml"); if (vendorMacPermission.exists()) { sMacPermissions.add(vendorMacPermission); - } else { - // For backward compatibility. - sMacPermissions.add(new File(Environment.getVendorDirectory(), - "/etc/selinux/nonplat_mac_permissions.xml")); } // ODM mac permissions (optional). diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 6c1ef2eda41d..a4f8087a2a07 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -198,6 +198,7 @@ final class DefaultPermissionGrantPolicy { private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>(); static { SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS); + SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); } private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 0958bcb6cc14..a3b6b8221843 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1252,7 +1252,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (op < 0) { // Bg location is one-off runtime modifier permission and has no app op if (sPlatformPermissions.contains(permission) - && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) { + && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission) + && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) { Slog.wtf(LOG_TAG, "Platform runtime permission " + permission + " with no app op defined!"); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index d1b99380b093..c9fd12219562 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -23,6 +23,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; +import static android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; @@ -108,6 +109,7 @@ import android.util.DebugUtils; import android.util.EventLog; import android.util.IntArray; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -196,6 +198,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt /** All nearby devices permissions */ private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>(); + // TODO: This is a placeholder. Replace with actual implementation + private static final List<String> NOTIFICATION_PERMISSIONS = new ArrayList<>(); + /** * All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are * implicitly added to a package @@ -4636,23 +4641,231 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt return true; } - private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId, - @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, - @UserIdInt int[] userIds) { - // If previousAppId is not Process.INVALID_UID, the package is performing a migration out - // of a shared user group. Operations we need to do before calling updatePermissions(): - // - Retrieve the original uid permission state and create a copy of it as the new app's - // uid state. The new permission state will be properly updated in updatePermissions(). - // - Remove the app from the original shared user group. Other apps in the shared - // user group will perceive as if the original app is uninstalled. - if (previousAppId != Process.INVALID_UID) { - final PackageStateInternal ps = - mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); + private boolean isEffectivelyGranted(PermissionState state) { + final int flags = state.getFlags(); + final int denyMask = FLAG_PERMISSION_REVIEW_REQUIRED + | FLAG_PERMISSION_REVOKED_COMPAT + | FLAG_PERMISSION_ONE_TIME; + + if ((flags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) { + return true; + } else if ((flags & FLAG_PERMISSION_POLICY_FIXED) != 0) { + return (flags & FLAG_PERMISSION_REVOKED_COMPAT) == 0 && state.isGranted(); + } else if ((flags & denyMask) != 0) { + return false; + } else { + return state.isGranted(); + } + } + + /** + * Merge srcState into destState. Return [granted, flags]. + */ + private Pair<Boolean, Integer> mergePermissionState(int appId, + PermissionState srcState, PermissionState destState) { + // This merging logic prioritizes the shared permission state (destState) over + // the current package's state (srcState), because an uninstallation of a previously + // unrelated app (the updated system app) should not affect the functionality of + // existing apps (other apps in the shared UID group). + + final int userSettableMask = FLAG_PERMISSION_USER_SET + | FLAG_PERMISSION_USER_FIXED + | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; + + final int defaultGrantMask = FLAG_PERMISSION_GRANTED_BY_DEFAULT + | FLAG_PERMISSION_GRANTED_BY_ROLE; + + final int priorityFixedMask = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED; + + final int priorityMask = defaultGrantMask | priorityFixedMask; + + final int destFlags = destState.getFlags(); + final boolean destIsGranted = isEffectivelyGranted(destState); + + final int srcFlags = srcState.getFlags(); + final boolean srcIsGranted = isEffectivelyGranted(srcState); + + final int combinedFlags = destFlags | srcFlags; + + /* Merge flags */ + + int newFlags = 0; + + // Inherit user set flags only from dest as we want to preserve the + // user preference of destState, not the one of the current package. + newFlags |= (destFlags & userSettableMask); + + // Inherit all exempt flags + newFlags |= (combinedFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT); + // If no exempt flags are set, set APPLY_RESTRICTION + if ((newFlags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) { + newFlags |= FLAG_PERMISSION_APPLY_RESTRICTION; + } + + // Inherit all priority flags + newFlags |= (combinedFlags & priorityMask); + + // If no priority flags are set, inherit REVOKE_WHEN_REQUESTED + if ((combinedFlags & priorityMask) == 0) { + newFlags |= (combinedFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); + } + + // Handle REVIEW_REQUIRED + if ((newFlags & priorityFixedMask) == 0) { + if (NOTIFICATION_PERMISSIONS.contains(srcState.getName())) { + // For notification permissions, inherit from both states + // if no priority FIXED flags are set + newFlags |= (combinedFlags & FLAG_PERMISSION_REVIEW_REQUIRED); + } else if ((newFlags & priorityMask) == 0) { + // Else inherit from destState if no priority flags are set + newFlags |= (destFlags & FLAG_PERMISSION_REVIEW_REQUIRED); + } + } + + /* Determine effective grant state */ + + final boolean effectivelyGranted; + if ((newFlags & FLAG_PERMISSION_SYSTEM_FIXED) != 0) { + effectivelyGranted = true; + } else if ((destFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) { + // If this flag comes from destState, preserve its state + effectivelyGranted = destIsGranted; + } else if ((srcFlags & FLAG_PERMISSION_POLICY_FIXED) != 0) { + effectivelyGranted = destIsGranted || srcIsGranted; + // If this flag comes from srcState, preserve flag only if + // there is no conflict + if (destIsGranted != srcIsGranted) { + newFlags &= ~FLAG_PERMISSION_POLICY_FIXED; + } + } else if ((destFlags & defaultGrantMask) != 0) { + // If a permission state has default grant flags and is not + // granted, this meant user has overridden the grant state. + // Respect the user's preference on destState. + // Due to this reason, if this flag comes from destState, + // preserve its state + effectivelyGranted = destIsGranted; + } else if ((srcFlags & defaultGrantMask) != 0) { + effectivelyGranted = destIsGranted || srcIsGranted; + } else if ((destFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { + // Similar reason to defaultGrantMask, if this flag comes + // from destState, preserve its state + effectivelyGranted = destIsGranted; + } else if ((srcFlags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { + effectivelyGranted = destIsGranted || srcIsGranted; + // If this flag comes from srcState, remove this flag if + // destState is already granted to prevent revocation. + if (destIsGranted) { + newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; + } + } else { + // If still not determined, fallback to destState. + effectivelyGranted = destIsGranted; + } + + /* Post-processing / fix ups */ + + if (!effectivelyGranted) { + // If not effectively granted, inherit AUTO_REVOKED + newFlags |= (combinedFlags & FLAG_PERMISSION_AUTO_REVOKED); + + // REVOKE_WHEN_REQUESTED make no sense when denied + newFlags &= ~FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; + } else { + // REVIEW_REQUIRED make no sense when granted + newFlags &= ~FLAG_PERMISSION_REVIEW_REQUIRED; + } + + if (effectivelyGranted != destIsGranted) { + // Remove user set flags if state changes + newFlags &= ~userSettableMask; + } + + // Fix permission state based on targetSdk of the shared UID + final boolean newGrantState; + if (!effectivelyGranted && isPermissionSplitFromNonRuntime( + srcState.getName(), + mPackageManagerInt.getUidTargetSdkVersion(appId))) { + // Even though effectively denied, it has to be set to granted + // for backwards compatibility + newFlags |= FLAG_PERMISSION_REVOKED_COMPAT; + newGrantState = true; + } else { + // Either it's effectively granted, or it targets a high enough API level + // to handle this permission properly + newGrantState = effectivelyGranted; + } + + return new Pair<>(newGrantState, newFlags); + } + + /** + * This method handles permission migration of packages leaving/joining shared UID + */ + private void handleAppIdMigration(@NonNull AndroidPackage pkg, int previousAppId) { + final PackageStateInternal ps = + mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); + + if (ps.getSharedUser() != null) { + // The package is joining a shared user group. This can only happen when a system + // app left shared UID with an update, and then the update is uninstalled. + // If no apps remain in its original shared UID group, clone the current + // permission state to the shared appId; or else, merge the current permission + // state into the shared UID state. + + synchronized (mLock) { + for (final int userId : getAllUserIds()) { + final UserPermissionState userState = mState.getOrCreateUserState(userId); + + // This is the permission state the package was using + final UidPermissionState uidState = userState.getUidState(previousAppId); + if (uidState == null) { + continue; + } + + // This is the shared UID permission state the package wants to join + final UidPermissionState sharedUidState = userState.getUidState(ps.getAppId()); + if (sharedUidState == null) { + // No apps remain in the shared UID group, clone permissions + userState.createUidStateWithExisting(ps.getAppId(), uidState); + } else { + final List<PermissionState> states = uidState.getPermissionStates(); + final int count = states.size(); + for (int i = 0; i < count; ++i) { + final PermissionState srcState = states.get(i); + final PermissionState destState = + sharedUidState.getPermissionState(srcState.getName()); + if (destState != null) { + // Merge the 2 permission states + Pair<Boolean, Integer> newState = + mergePermissionState(ps.getAppId(), srcState, destState); + sharedUidState.putPermissionState(srcState.getPermission(), + newState.first, newState.second); + } else { + // Simply copy the permission state over + sharedUidState.putPermissionState(srcState.getPermission(), + srcState.isGranted(), srcState.getFlags()); + } + } + } + + // Remove permissions for the previous appId + userState.removeUidState(previousAppId); + } + } + } else { + // The package is migrating out of a shared user group. + // Operations we need to do before calling updatePermissions(): + // - Retrieve the original uid permission state and create a copy of it as the + // new app's uid state. The new permission state will be properly updated in + // updatePermissions(). + // - Remove the app from the original shared user group. Other apps in the shared + // user group will perceive as if the original app is uninstalled. + final List<AndroidPackage> origSharedUserPackages = mPackageManagerInt.getPackagesForAppId(previousAppId); synchronized (mLock) { - // All users are affected for (final int userId : getAllUserIds()) { // Retrieve the original uid state final UserPermissionState userState = mState.getUserState(userId); @@ -4679,6 +4892,14 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } } + } + + private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId, + @NonNull PermissionManagerServiceInternal.PackageInstalledParams params, + @UserIdInt int[] userIds) { + if (previousAppId != Process.INVALID_UID) { + handleAppIdMigration(pkg, previousAppId); + } updatePermissions(pkg.getPackageName(), pkg); for (final int userId : userIds) { addAllowlistedRestrictedPermissionsInternal(pkg, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index cde5273b38dd..6c17bf193e5b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4121,8 +4121,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean containsShowWhenLocked = containsShowWhenLockedWindow(); if (containsDismissKeyguard != mLastContainsDismissKeyguardWindow || containsShowWhenLocked != mLastContainsShowWhenLockedWindow) { - mWmService.notifyKeyguardFlagsChanged(null /* callback */, - getDisplayContent().getDisplayId()); + mDisplayContent.notifyKeyguardFlagsChanged(); } mLastContainsDismissKeyguardWindow = containsDismissKeyguard; mLastContainsShowWhenLockedWindow = containsShowWhenLocked; diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index b183281e0f52..bce288311e13 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -55,12 +55,12 @@ class ActivityRecordInputSink { private final ActivityRecord mActivityRecord; private final boolean mIsCompatEnabled; + private final String mName; // Hold on to InputEventReceiver to prevent it from getting GCd. private InputEventReceiver mInputEventReceiver; private InputWindowHandleWrapper mInputWindowHandleWrapper; - private final String mName = Integer.toHexString(System.identityHashCode(this)) - + " ActivityRecordInputSink"; + private int mRapidTouchCount = 0; private IBinder mToken; private boolean mDisabled = false; @@ -69,6 +69,8 @@ class ActivityRecordInputSink { mActivityRecord = activityRecord; mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES, mActivityRecord.getUid()); + mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink " + + mActivityRecord.mActivityComponent.getShortClassName(); } public void applyChangesToSurfaceIfChanged( @@ -91,11 +93,6 @@ class ActivityRecordInputSink { ANIMATION_TYPE_APP_TRANSITION)) { // TODO(b/208662670): Investigate if we can have feature active during animations. mInputWindowHandleWrapper.setToken(null); - } else if (mActivityRecord.mStartingData != null) { - // TODO(b/208659130): Remove this special case - // Don't block touches during splash screen. This is done to not show toasts for - // touches passing through splash screens. b/171772640 - mInputWindowHandleWrapper.setToken(null); } else { mInputWindowHandleWrapper.setToken(mToken); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 7fa98618ecb9..a9142ef85608 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -243,21 +243,6 @@ public abstract class ActivityTaskManagerInternal { int startFlags, @Nullable Bundle options, int userId); /** - * Called when Keyguard flags might have changed. - * - * @param callback Callback to run after activity visibilities have been reevaluated. This can - * be used from window manager so that when the callback is called, it's - * guaranteed that all apps have their visibility updated accordingly. - * @param displayId The id of the display where the keyguard flags changed. - */ - public abstract void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId); - - /** - * Called when the trusted state of Keyguard has changed. - */ - public abstract void notifyKeyguardTrustedChanged(); - - /** * Called after virtual display Id is updated by * {@link com.android.server.vr.Vr2dDisplay} with a specific * {@param vr2dDisplayId}. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 173545c30291..15ebe2846513 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -63,7 +63,6 @@ import static android.provider.Settings.System.FONT_SCALE; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_WAKE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; @@ -5415,42 +5414,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { false /*validateIncomingUser*/); } - @Override - public void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) { - synchronized (mGlobalLock) { - - // We might change the visibilities here, so prepare an empty app transition which - // might be overridden later if we actually change visibilities. - final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId); - if (dc == null) { - return; - } - final boolean wasTransitionSet = dc.mAppTransition.isTransitionSet(); - if (!wasTransitionSet) { - dc.prepareAppTransition(TRANSIT_NONE); - } - mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - - // If there was a transition set already we don't want to interfere with it as we - // might be starting it too early. - if (!wasTransitionSet) { - dc.executeAppTransition(); - } - } - if (callback != null) { - callback.run(); - } - } - - @Override - public void notifyKeyguardTrustedChanged() { - synchronized (mGlobalLock) { - if (mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { - mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - } - } - } - /** * Called after virtual display Id is updated by * {@link com.android.server.vr.Vr2dDisplay} with a specific diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e80a9b9f2a8e..8ede0160cdce 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -80,6 +80,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.DisplayAreaOrganizer.FEATURE_IME; @@ -5914,6 +5915,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** + * Notifies that some Keyguard flags have changed and the visibilities of the activities may + * need to be reevaluated. + */ + void notifyKeyguardFlagsChanged() { + if (!isKeyguardLocked()) { + // If keyguard is not locked, the change of flags won't affect activity visibility. + return; + } + // We might change the visibilities here, so prepare an empty app transition which might be + // overridden later if we actually change visibilities. + final boolean wasTransitionSet = mAppTransition.isTransitionSet(); + if (!wasTransitionSet) { + prepareAppTransition(TRANSIT_NONE); + } + mRootWindowContainer.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + + // If there was a transition set already we don't want to interfere with it as we might be + // starting it too early. + if (!wasTransitionSet) { + executeAppTransition(); + } + } + + /** * Check if the display has {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied. */ boolean canShowWithInsecureKeyguard() { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 4768b27a4f9e..5f7bd8dfe657 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1710,7 +1710,8 @@ public class DisplayPolicy { if (mShowingDream != mLastShowingDream) { mLastShowingDream = mShowingDream; - mService.notifyShowingDreamChanged(); + // Notify that isShowingDreamLw (which is checked in KeyguardController) has changed. + mDisplayContent.notifyKeyguardFlagsChanged(); } mService.mPolicy.setAllowLockscreenWhenOn(getDisplayId(), mAllowLockscreenWhenOn); @@ -2377,8 +2378,7 @@ public class DisplayPolicy { @VisibleForTesting int updateLightNavigationBarLw(int appearance, WindowState navColorWin) { - if (navColorWin == null || navColorWin.isDimming() - || !isLightBarAllowed(navColorWin, Type.navigationBars())) { + if (navColorWin == null || !isLightBarAllowed(navColorWin, Type.navigationBars())) { // Clear the light flag while not allowed. appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS; return appearance; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index c7b13eb96dfa..dcb28d25ee5d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1037,7 +1037,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) { + final boolean curDisplayInTransitNotAnimate = + // legacy transition + (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) + // shell transition + || (curDisplay.mTransitionController.isShellTransitionsEnabled() + && !curDisplay.mTransitionController.isPlaying()); + if (curDisplayInTransitNotAnimate) { // We have finished the animation of an app transition. To do this, we have // delayed a lot of operations like showing and hiding apps, moving apps in // Z-order, etc. diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c6948eee5a0c..873e18d0e5cf 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -25,6 +25,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; @@ -70,6 +71,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.view.SurfaceControl; +import android.view.WindowManager; import android.view.animation.Animation; import android.window.RemoteTransition; import android.window.TransitionInfo; @@ -82,6 +84,8 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; /** * Represents a logical transition. @@ -90,6 +94,9 @@ import java.util.ArrayList; class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "Transition"; + /** The default package for resources */ + private static final String DEFAULT_PACKAGE = "android"; + /** The transition has been created and is collecting, but hasn't formally started. */ private static final int STATE_COLLECTING = 0; @@ -548,7 +555,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Resolve the animating targets from the participants mTargets = calculateTargets(mParticipants, mChanges); final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges); - info.setAnimationOptions(mOverrideOptions); + if (mOverrideOptions != null) { + info.setAnimationOptions(mOverrideOptions); + } // TODO(b/188669821): Move to animation impl in shell. handleLegacyRecentsStartBehavior(dc, info); @@ -1249,9 +1258,80 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe out.addChange(change); } + final WindowManager.LayoutParams animLp = + getLayoutParamsForAnimationsStyle(type, sortedTargets); + if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING + && animLp.windowAnimations != 0) { + // Don't send animation options if no windowAnimations have been set or if the we are + // running an app starting animation, in which case we don't want the app to be able to + // change its animation directly. + TransitionInfo.AnimationOptions animOptions = + TransitionInfo.AnimationOptions.makeAnimOptionsFromLayoutParameters(animLp); + out.setAnimationOptions(animOptions); + } + return out; } + private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type, + ArrayList<WindowContainer> sortedTargets) { + // Find the layout params of the top-most application window that is part of the + // transition, which is what will control the animation theme. + final ArraySet<Integer> activityTypes = new ArraySet<>(); + for (WindowContainer target : sortedTargets) { + if (target.asActivityRecord() != null) { + activityTypes.add(target.getActivityType()); + } else if (target.asWindowToken() == null && target.asWindowState() == null) { + // We don't want app to customize animations that are not activity to activity. + // Activity-level transitions can only include activities, wallpaper and subwindows. + // Anything else is not a WindowToken nor a WindowState and is "higher" in the + // hierarchy which means we are no longer in an activity transition. + return null; + } + } + if (activityTypes.isEmpty()) { + // We don't want app to be able to customize transitions that are not activity to + // activity through the layout parameter animation style. + return null; + } + final ActivityRecord animLpActivity = + findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes); + final WindowState mainWindow = animLpActivity != null + ? animLpActivity.findMainWindow() : null; + return mainWindow != null ? mainWindow.mAttrs : null; + } + + private static ActivityRecord findAnimLayoutParamsActivityRecord( + List<WindowContainer> sortedTargets, + @TransitionType int transit, ArraySet<Integer> activityTypes) { + // Remote animations always win, but fullscreen windows override non-fullscreen windows. + ActivityRecord result = lookForTopWindowWithFilter(sortedTargets, + w -> w.getRemoteAnimationDefinition() != null + && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes)); + if (result != null) { + return result; + } + result = lookForTopWindowWithFilter(sortedTargets, + w -> w.fillsParent() && w.findMainWindow() != null); + if (result != null) { + return result; + } + return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null); + } + + private static ActivityRecord lookForTopWindowWithFilter(List<WindowContainer> sortedTargets, + Predicate<ActivityRecord> filter) { + for (WindowContainer target : sortedTargets) { + final ActivityRecord activityRecord = target.asTaskFragment() != null + ? target.asTaskFragment().getTopNonFinishingActivity() + : target.asActivityRecord(); + if (activityRecord != null && filter.test(activityRecord)) { + return activityRecord; + } + } + return null; + } + private static int getTaskRotationAnimation(@NonNull Task task) { final ActivityRecord top = task.getTopVisibleActivity(); if (top == null) return ROTATION_ANIMATION_UNSPECIFIED; diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java index 4007661cf1a0..5e963cc5ae8e 100644 --- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java +++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java @@ -135,8 +135,8 @@ class UnknownAppVisibilityController { int state = mUnknownApps.get(activity); if (state == UNKNOWN_STATE_WAITING_RELAYOUT || activity.mStartingWindow != null) { mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE); - mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated, - activity.getDisplayContent().getDisplayId()); + mDisplayContent.notifyKeyguardFlagsChanged(); + notifyVisibilitiesUpdated(); } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index e24be378d29a..24493e2541e1 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -298,9 +298,9 @@ class WallpaperController { } boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) { - final Rect parentFrame = wallpaperWin.getParentFrame(); - final int dw = parentFrame.width(); - final int dh = parentFrame.height(); + final Rect bounds = wallpaperWin.getLastReportedBounds(); + final int dw = bounds.width(); + final int dh = bounds.height(); int xOffset = 0; int yOffset = 0; @@ -448,6 +448,13 @@ class WallpaperController { private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { WindowState target = mWallpaperTarget; + if (target == null && changingTarget.mToken.isVisible() + && changingTarget.mTransitionController.inTransition()) { + // If the wallpaper target was cleared during transition, still allows the visible + // window which may have been requested to be invisible to update the offset, e.g. + // zoom effect from home. + target = changingTarget; + } if (target != null) { if (target.mWallpaperX >= 0) { mLastWallpaperX = target.mWallpaperX; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 7e84dbbec311..bef7810f5d48 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3181,6 +3181,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** Cheap way of doing cast and instanceof. */ + WindowState asWindowState() { + return null; + } + + /** Cheap way of doing cast and instanceof. */ ActivityRecord asActivityRecord() { return null; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 86775f629df0..ded0dd7eb693 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3026,17 +3026,13 @@ public class WindowManagerService extends IWindowManager.Stub aspectRatio); } - /** - * Notifies window manager that {@link DisplayPolicy#isShowingDreamLw} has changed. - */ - public void notifyShowingDreamChanged() { - // TODO(multi-display): support show dream in multi-display. - notifyKeyguardFlagsChanged(null /* callback */, DEFAULT_DISPLAY); - } - @Override public void notifyKeyguardTrustedChanged() { - mAtmInternal.notifyKeyguardTrustedChanged(); + synchronized (mGlobalLock) { + if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { + mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + } + } } @Override @@ -3088,15 +3084,6 @@ public class WindowManagerService extends IWindowManager.Stub return getDefaultDisplayContentLocked().mAppTransition.isIdle(); } - /** - * Notifies activity manager that some Keyguard flags have changed and that it needs to - * reevaluate the visibilities of the activities. - * @param callback Runnable to be called when activity manager is done reevaluating visibilities - */ - void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) { - mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId); - } - // ------------------------------------------------------------- // Misc IWindowSession methods diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 0b9174210a19..f6729c552cc3 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -844,6 +844,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } }; + @Override + WindowState asWindowState() { + return this; + } + /** * @see #setSurfaceTranslationY(int) */ @@ -2882,6 +2887,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mLastReportedConfiguration.getMergedConfiguration(); } + /** Returns the last window configuration bounds reported to the client. */ + Rect getLastReportedBounds() { + final Rect bounds = getLastReportedConfiguration().windowConfiguration.getBounds(); + return !bounds.isEmpty() ? bounds : getBounds(); + } + void adjustStartingWindowFlags() { if (mAttrs.type == TYPE_BASE_APPLICATION && mActivityRecord != null && mActivityRecord.mStartingWindow != null) { @@ -5230,6 +5241,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Child window follows parent's scale. return; } + if (!isVisibleRequested() && !(mIsWallpaper && mToken.isVisible())) { + // Skip if it is requested to be invisible, but if it is wallpaper, it may be in + // transition that still needs to update the scale for zoom effect. + return; + } float newHScale = mHScale * mGlobalScale * mWallpaperScale; float newVScale = mVScale * mGlobalScale * mWallpaperScale; if (mLastHScale != newHScale || @@ -5249,7 +5265,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateSurfacePositionNonOrganized(); // Send information to SurfaceFlinger about the priority of the current window. updateFrameRateSelectionPriorityIfNeeded(); - if (isVisibleRequested()) updateScaleIfNeeded(); + updateScaleIfNeeded(); mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); super.prepareSurfaces(); @@ -5275,11 +5291,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSurfacePosition); if (mWallpaperScale != 1f) { - DisplayInfo displayInfo = getDisplayInfo(); + final Rect bounds = getLastReportedBounds(); Matrix matrix = mTmpMatrix; matrix.setTranslate(mXOffset, mYOffset); - matrix.postScale(mWallpaperScale, mWallpaperScale, displayInfo.logicalWidth / 2f, - displayInfo.logicalHeight / 2f); + matrix.postScale(mWallpaperScale, mWallpaperScale, bounds.exactCenterX(), + bounds.exactCenterY()); matrix.getValues(mTmpMatrixArray); mSurfacePosition.offset(Math.round(mTmpMatrixArray[Matrix.MTRANS_X]), Math.round(mTmpMatrixArray[Matrix.MTRANS_Y])); diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index c5a69e2f84b7..6aa632388ffd 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -2292,6 +2292,11 @@ static jboolean nativeFlushSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, ji sensorType)); } +static void nativeCancelCurrentTouch(JNIEnv* env, jclass /* clazz */, jlong ptr) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + im->getInputManager()->getDispatcher().cancelCurrentTouch(); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -2371,6 +2376,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeEnableSensor", "(JIIII)Z", (void*)nativeEnableSensor}, {"nativeDisableSensor", "(JII)V", (void*)nativeDisableSensor}, {"nativeFlushSensor", "(JII)Z", (void*)nativeFlushSensor}, + {"nativeCancelCurrentTouch", "(J)V", (void*)nativeCancelCurrentTouch}, }; #define FIND_CLASS(var, className) \ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 266b65686141..df8953c318dc 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -7459,7 +7459,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId"); final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)); + Preconditions.checkCallAuthorization( + hasFullCrossUsersPermission(caller, userHandle) && canQueryAdminPolicy(caller)); synchronized (getLockObject()) { DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM); diff --git a/services/proguard.flags b/services/proguard.flags index 30dd6cf545b9..5d01d3e7f85c 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -1,11 +1,21 @@ # TODO(b/196084106): Refine and optimize this configuration. Note that this # configuration is only used when `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA=true`. -keep,allowoptimization,allowaccessmodification class ** { - *; + !synthetic *; } # Various classes subclassed in ethernet-service (avoid marking final). -keep public class android.net.** { *; } # Referenced via CarServiceHelperService in car-frameworks-service (avoid removing). --keep public class com.android.server.utils.Slogf { *; }
\ No newline at end of file +-keep public class com.android.server.utils.Slogf { *; } + +# Allows making private and protected methods/fields public as part of +# optimization. This enables inlining of trivial getter/setter methods. +-allowaccessmodification + +# Disallow accessmodification for soundtrigger classes. Logging via reflective +# public member traversal can cause infinite loops. See b/210901706. +-keep,allowoptimization class com.android.server.soundtrigger_middleware.** { + !synthetic *; +} diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 48a8b1bca99b..8538603d4180 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -59,6 +59,7 @@ android_test { "mockingservicestests-utils-mockito", "servicestests-core-utils", "testables", + "kotlin-test", // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows "testng", ], diff --git a/services/tests/mockingservicestests/assets/GameServiceSelectorTest/game_service_metadata_valid.xml b/services/tests/mockingservicestests/assets/GameServiceSelectorTest/game_service_metadata_valid.xml new file mode 100644 index 000000000000..4720085e03fc --- /dev/null +++ b/services/tests/mockingservicestests/assets/GameServiceSelectorTest/game_service_metadata_valid.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<game-service xmlns:android="http://schemas.android.com/apk/res/android" + android:sessionService="com.game.service.provider.GameSessionService"/> diff --git a/services/tests/mockingservicestests/res/xml/game_service_metadata_no_session_service.xml b/services/tests/mockingservicestests/res/xml/game_service_metadata_no_session_service.xml new file mode 100644 index 000000000000..ebd5103a24ff --- /dev/null +++ b/services/tests/mockingservicestests/res/xml/game_service_metadata_no_session_service.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<game-service/> diff --git a/services/tests/mockingservicestests/res/xml/game_service_metadata_valid.xml b/services/tests/mockingservicestests/res/xml/game_service_metadata_valid.xml new file mode 100644 index 000000000000..8ee3cceee8ea --- /dev/null +++ b/services/tests/mockingservicestests/res/xml/game_service_metadata_valid.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<game-service xmlns:android="http://schemas.android.com/apk/res/android" + android:gameSessionService="com.game.service.provider.GameSessionService"/> diff --git a/services/tests/mockingservicestests/res/xml/game_service_metadata_wrong_first_tag.xml b/services/tests/mockingservicestests/res/xml/game_service_metadata_wrong_first_tag.xml new file mode 100644 index 000000000000..6bc0eac551bc --- /dev/null +++ b/services/tests/mockingservicestests/res/xml/game_service_metadata_wrong_first_tag.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<wrong-tag xmlns:android="http://schemas.android.com/apk/res/android" + android:gameSessionService="com.game.service.provider.GameSessionService"/> diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeGameClassifier.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameClassifier.java new file mode 100644 index 000000000000..060b773d7b7b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameClassifier.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 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.app; + +import android.annotation.NonNull; +import android.os.UserHandle; + +import java.util.HashSet; + +/** + * Fake implementation of {@link GameClassifier} used for tests. + * + * By default, all packages are considers not games. A package may be marked as a game using + * {@link #recordGamePackage(String)}. + */ +final class FakeGameClassifier implements GameClassifier { + private final HashSet<String> mGamePackages = new HashSet<>(); + + /** + * Marks the given {@code packageName} as a game. + */ + public void recordGamePackage(String packageName) { + mGamePackages.add(packageName); + } + + @Override + public boolean isGame(@NonNull String packageName, UserHandle userHandle) { + return mGamePackages.contains(packageName); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeGameServiceProviderInstance.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameServiceProviderInstance.java new file mode 100644 index 000000000000..98142f5774a0 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeGameServiceProviderInstance.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 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.app; + + +/** + * Fake implementation of {@link GameServiceProviderInstance} used for tests. + */ +final class FakeGameServiceProviderInstance implements GameServiceProviderInstance { + private boolean mRunning; + + @Override + public void start() { + mRunning = true; + } + + @Override + public void stop() { + mRunning = false; + } + + /** + * Returns {@code true} if the instance is currently running. + */ + public boolean getIsRunning() { + return mRunning; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java new file mode 100644 index 000000000000..0ae509ec0fbc --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/app/FakeServiceConnector.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021 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.app; + + +import android.os.IInterface; + +import com.android.internal.infra.AndroidFuture; +import com.android.internal.infra.ServiceConnector; + +import java.util.concurrent.CompletableFuture; + +/** + * Fake implementation of {@link ServiceConnector<T>} used for tests. + * + * Tests provide a service instance via {@link #FakeServiceConnector(IInterface)} that will be + * connected to and used to fulfill service jobs. + */ +final class FakeServiceConnector<T extends IInterface> implements + ServiceConnector<T> { + private final T mService; + private boolean mIsConnected; + private int mConnectCount = 0; + + FakeServiceConnector(T service) { + mService = service; + } + + @Override + public boolean run(VoidJob<T> job) { + AndroidFuture<Void> unusedFuture = post(job); + return true; + } + + @Override + public AndroidFuture<Void> post(VoidJob<T> job) { + markPossibleConnection(); + + return postForResult(job); + } + + @Override + public <R> AndroidFuture<R> postForResult(Job<T, R> job) { + markPossibleConnection(); + + AndroidFuture<R> androidFuture = new AndroidFuture(); + try { + androidFuture.complete(job.run(mService)); + } catch (Exception ex) { + androidFuture.completeExceptionally(ex); + } + return androidFuture; + } + + @Override + @SuppressWarnings("FutureReturnValueIgnored") + public <R> AndroidFuture<R> postAsync(Job<T, CompletableFuture<R>> job) { + markPossibleConnection(); + AndroidFuture<R> androidFuture = new AndroidFuture(); + + try { + CompletableFuture<R> future = job.run(mService); + future.whenComplete((result, exception) -> { + if (exception != null) { + androidFuture.completeExceptionally(exception); + } else { + androidFuture.complete(result); + } + }); + } catch (Exception ex) { + androidFuture.completeExceptionally(ex); + } + + return androidFuture; + } + + @Override + public AndroidFuture<T> connect() { + markPossibleConnection(); + return AndroidFuture.completedFuture(mService); + } + + @Override + public void unbind() { + mIsConnected = false; + } + + private void markPossibleConnection() { + if (mIsConnected) { + return; + } + + mConnectCount += 1; + mIsConnected = true; + } + + /** + * Returns {@code true} if the underlying service is connected. + */ + public boolean getIsConnected() { + return mIsConnected; + } + + /** + * Returns the number of times a connection was established with the underlying service. + */ + public int getConnectCount() { + return mConnectCount; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java new file mode 100644 index 000000000000..0545fde3e921 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2021 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.app; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.ConcurrentUtils; +import com.android.server.SystemService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + + +/** Unit tests for {@link GameServiceController}. */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public final class GameServiceControllerTest { + private static final UserHandle USER_HANDLE_10 = new UserHandle(10); + private static final UserHandle USER_HANDLE_11 = new UserHandle(11); + private static final SystemService.TargetUser USER_10 = user(10); + private static final SystemService.TargetUser USER_11 = user(11); + private static final String PROVIDER_A_PACKAGE_NAME = "com.provider.a"; + private static final ComponentName PROVIDER_A_SERVICE_A = + new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceA"); + private static final ComponentName PROVIDER_A_SERVICE_B = + new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceB"); + + private MockitoSession mMockingSession; + private GameServiceController mGameServiceManager; + @Mock + private GameServiceProviderSelector mMockGameServiceProviderSelector; + @Mock + private GameServiceProviderInstanceFactory mMockGameServiceProviderInstanceFactory; + + @Before + public void setUp() throws Exception { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + + mGameServiceManager = new GameServiceController( + ConcurrentUtils.DIRECT_EXECUTOR, + mMockGameServiceProviderSelector, + mMockGameServiceProviderInstanceFactory); + } + + @After + public void tearDown() { + mMockingSession.finishMocking(); + } + + @Test + public void notifyUserStarted_hasNotCompletedBoot_doesNothing() { + mGameServiceManager.notifyUserStarted(USER_10); + + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + } + + @Test + public void notifyUserStarted_createsAndStartsNewInstance() { + GameServiceProviderConfiguration configurationA = + new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B); + FakeGameServiceProviderInstance instanceA = + seedConfigurationForUser(USER_10, configurationA); + + mGameServiceManager.onBootComplete(); + mGameServiceManager.notifyUserStarted(USER_10); + + verify(mMockGameServiceProviderInstanceFactory).create(configurationA); + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + assertThat(instanceA.getIsRunning()).isTrue(); + } + + @Test + public void notifyUserStarted_sameUser_doesNotCreateNewInstance() { + GameServiceProviderConfiguration configurationA = + new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B); + FakeGameServiceProviderInstance instanceA = + seedConfigurationForUser(USER_10, configurationA); + + mGameServiceManager.onBootComplete(); + mGameServiceManager.notifyUserStarted(USER_10); + mGameServiceManager.notifyUserStarted(USER_10); + + verify(mMockGameServiceProviderInstanceFactory).create(configurationA); + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + assertThat(instanceA.getIsRunning()).isTrue(); + } + + @Test + public void notifyUserUnlocking_noForegroundUser_ignores() { + GameServiceProviderConfiguration configurationA = + new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B); + FakeGameServiceProviderInstance instanceA = + seedConfigurationForUser(USER_10, configurationA); + + mGameServiceManager.onBootComplete(); + mGameServiceManager.notifyUserUnlocking(USER_10); + + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + assertThat(instanceA.getIsRunning()).isFalse(); + } + + @Test + public void notifyUserUnlocking_sameAsForegroundUser_evaluatesProvider() { + GameServiceProviderConfiguration configurationA = + new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B); + seedNoConfigurationForUser(USER_10); + + mGameServiceManager.onBootComplete(); + mGameServiceManager.notifyUserStarted(USER_10); + FakeGameServiceProviderInstance instanceA = + seedConfigurationForUser(USER_10, configurationA); + mGameServiceManager.notifyUserUnlocking(USER_10); + + verify(mMockGameServiceProviderInstanceFactory).create(configurationA); + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + assertThat(instanceA.getIsRunning()).isTrue(); + } + + @Test + public void notifyUserUnlocking_differentFromForegroundUser_ignores() { + GameServiceProviderConfiguration configurationA = + new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B); + seedNoConfigurationForUser(USER_10); + + mGameServiceManager.onBootComplete(); + mGameServiceManager.notifyUserStarted(USER_10); + FakeGameServiceProviderInstance instanceA = + seedConfigurationForUser(USER_11, configurationA); + mGameServiceManager.notifyUserUnlocking(USER_11); + + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + assertThat(instanceA.getIsRunning()).isFalse(); + } + + @Test + public void + notifyNewForegroundUser_differentUser_stopsPreviousInstanceAndThenStartsNewInstance() { + GameServiceProviderConfiguration configurationA = + new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B); + FakeGameServiceProviderInstance instanceA = + seedConfigurationForUser(USER_10, configurationA); + GameServiceProviderConfiguration configurationB = + new GameServiceProviderConfiguration(USER_HANDLE_11, PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B); + FakeGameServiceProviderInstance instanceB = seedConfigurationForUser(USER_11, + configurationB); + InOrder instancesInOrder = Mockito.inOrder(instanceA, instanceB); + + mGameServiceManager.onBootComplete(); + mGameServiceManager.notifyUserStarted(USER_10); + mGameServiceManager.notifyNewForegroundUser(USER_11); + + verify(mMockGameServiceProviderInstanceFactory).create(configurationA); + verify(mMockGameServiceProviderInstanceFactory).create(configurationB); + instancesInOrder.verify(instanceA).start(); + instancesInOrder.verify(instanceA).stop(); + instancesInOrder.verify(instanceB).start(); + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + assertThat(instanceA.getIsRunning()).isFalse(); + assertThat(instanceB.getIsRunning()).isTrue(); + } + + private void seedNoConfigurationForUser(SystemService.TargetUser user) { + when(mMockGameServiceProviderSelector.get(user)).thenReturn(null); + } + + private FakeGameServiceProviderInstance seedConfigurationForUser(SystemService.TargetUser user, + GameServiceProviderConfiguration configuration) { + when(mMockGameServiceProviderSelector.get(user)).thenReturn(configuration); + FakeGameServiceProviderInstance instanceForConfiguration = + spy(new FakeGameServiceProviderInstance()); + when(mMockGameServiceProviderInstanceFactory.create(configuration)) + .thenReturn(instanceForConfiguration); + + return instanceForConfiguration; + } + + private static SystemService.TargetUser user(int userId) { + UserInfo userInfo = new UserInfo(userId, "", "", UserInfo.FLAG_FULL); + return new SystemService.TargetUser(userInfo); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java deleted file mode 100644 index 8973a897e888..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java +++ /dev/null @@ -1,455 +0,0 @@ -/* - * Copyright (C) 2021 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.app; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.os.UserHandle; -import android.os.UserManager; -import android.platform.test.annotations.Presubmit; -import android.service.games.GameService; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.SystemService; - -import com.google.common.collect.ImmutableList; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoSession; - -import java.util.List; - - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public final class GameServiceControllerTests { - @Mock - private PackageManager mMockPackageManager; - @Mock - private Resources mMockResources; - @Mock - private Context mMockContext; - private MockitoSession mMockingSession; - - private static UserInfo eligibleUserInfo(int uid) { - return new UserInfo(uid, "", "", UserInfo.FLAG_FULL); - } - - private static UserInfo managedUserInfo(int uid) { - UserInfo userInfo = eligibleUserInfo(uid); - userInfo.userType = UserManager.USER_TYPE_PROFILE_MANAGED; - return userInfo; - } - - private static ResolveInfo resolveInfo(ServiceInfo serviceInfo) { - ResolveInfo resolveInfo = new ResolveInfo(); - resolveInfo.serviceInfo = serviceInfo; - return resolveInfo; - } - - private static ServiceInfo serviceInfo(String packageName, String name, boolean isEnabled) { - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.packageName = packageName; - applicationInfo.enabled = true; - - ServiceInfo serviceInfo = new ServiceInfo(); - serviceInfo.applicationInfo = applicationInfo; - serviceInfo.packageName = packageName; - serviceInfo.name = name; - serviceInfo.enabled = isEnabled; - return serviceInfo; - } - - private static SystemService.TargetUser managedTargetUser(int ineligibleUserId) { - return new SystemService.TargetUser(managedUserInfo(ineligibleUserId)); - } - - private static SystemService.TargetUser eligibleTargetUser(int userId) { - return new SystemService.TargetUser(eligibleUserInfo(userId)); - } - - private static UserHandle userWithId(int userId) { - return argThat(userInfo -> userInfo.getIdentifier() == userId); - } - - @Before - public void setUp() { - mMockingSession = mockitoSession() - .initMocks(this) - .startMocking(); - } - - @After - public void tearDown() { - mMockingSession.finishMocking(); - } - - @Test - public void testStartConnectionOnBootWithNoUser() { - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.onBootComplete(); - - verifyNoServiceBound(); - } - - @Test - public void testStartConnectionOnBootWithManagedUser() { - int userId = 12345; - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.notifyUserStarted(managedTargetUser(userId)); - gameServiceController.onBootComplete(); - - verifyNoServiceBound(); - } - - @Test - public void testStartConnectionOnBootWithUserAndNoSystemGamesServiceSet() { - seedSystemGameServicePackageName(""); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.notifyUserStarted(eligibleTargetUser(1000)); - gameServiceController.onBootComplete(); - - verifyNoServiceBound(); - } - - @Test - public void testStartConnectionOnBootWithUserAndSystemGamesServiceDoesNotExist() { - int userId = 12345; - String gameServicePackageName = "game.service.package"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of()); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.notifyUserStarted(eligibleTargetUser(userId)); - gameServiceController.onBootComplete(); - - verifyNoServiceBound(); - } - - @Test - public void testStartConnectionOnBootWithUserAndSystemGamesServiceSet() { - int userId = 12345; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.notifyUserStarted(eligibleTargetUser(userId)); - gameServiceController.onBootComplete(); - - verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, gameServiceComponent); - } - - @Test - public void testStartConnectionOnBootWithUserAndSystemGamesServiceNotEnabled() { - int userId = 12345; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, false)))); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.notifyUserStarted(eligibleTargetUser(userId)); - gameServiceController.onBootComplete(); - - verifyNoServiceBound(); - } - - @Test - public void testStartConnectionOnBootWithUserAndSystemGamesServiceHasMultipleComponents() { - int userId = 12345; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent1 = "game.service.package.example.GameService1"; - String gameServiceComponent2 = "game.service.package.example.GameService2"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent1, true)), - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent2, true)))); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.notifyUserStarted(eligibleTargetUser(userId)); - gameServiceController.onBootComplete(); - - verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, - gameServiceComponent1); - } - - @Test - public void testStartConnectionOnBootWithUserAndSystemGamesServiceHasDisabledComponent() { - int userId = 12345; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent1 = "game.service.package.example.GameService1"; - String gameServiceComponent2 = "game.service.package.example.GameService2"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent1, false)), - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent2, true)))); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.notifyUserStarted(eligibleTargetUser(userId)); - gameServiceController.onBootComplete(); - - verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, - gameServiceComponent2); - } - - @Test - public void testSwitchFromEligibleUserToEligibleUser() { - int userId1 = 1; - int userId2 = 2; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceResolveInfos(gameServicePackageName, userId2, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceToBindSuccessfully(); - - GameServiceController gameServiceController = new GameServiceController(mMockContext); - - gameServiceController.onBootComplete(); - gameServiceController.notifyUserStarted(eligibleTargetUser(userId1)); - - verifyServiceBoundForUserAndComponent(userId1, gameServicePackageName, - gameServiceComponent); - - gameServiceController.notifyNewForegroundUser(eligibleTargetUser(userId2)); - - verify(mMockContext).unbindService(any()); - verifyServiceBoundForUserAndComponent(userId2, gameServicePackageName, - gameServiceComponent); - } - - @Test - public void testSwitchFromEligibleUserToIneligibleUser() { - int eligibleUserId = 1; - int ineligibleUserId = 2; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, eligibleUserId, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceToBindSuccessfully(); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.onBootComplete(); - gameServiceController.notifyUserStarted(eligibleTargetUser(eligibleUserId)); - - verifyServiceBoundForUserAndComponent(eligibleUserId, gameServicePackageName, - gameServiceComponent); - - gameServiceController.notifyNewForegroundUser(managedTargetUser(ineligibleUserId)); - - verify(mMockContext).unbindService(any()); - } - - @Test - public void testSwitchFromIneligibleUserToEligibleUser() { - int eligibleUserId = 1; - int ineligibleUserId = 2; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, eligibleUserId, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceToBindSuccessfully(); - - GameServiceController gameServiceController = new GameServiceController(mMockContext); - - gameServiceController.onBootComplete(); - gameServiceController.notifyUserStarted(managedTargetUser(ineligibleUserId)); - - verifyNoServiceBound(); - - gameServiceController.notifyNewForegroundUser(eligibleTargetUser(eligibleUserId)); - - verifyServiceBoundForUserAndComponent(eligibleUserId, gameServicePackageName, - gameServiceComponent); - } - - @Test - public void testMultipleRunningUsers() { - int userId1 = 123; - int userId2 = 456; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceToBindSuccessfully(); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.onBootComplete(); - gameServiceController.notifyUserStarted(eligibleTargetUser(userId1)); - gameServiceController.notifyUserStarted(eligibleTargetUser(userId2)); - - verifyServiceBoundForUserAndComponent(userId1, gameServicePackageName, - gameServiceComponent); - verifyServiceNotBoundForUser(userId2); - verify(mMockContext, never()).unbindService(any()); - } - - @Test - public void testForegroundUserStopped() { - int userId = 123123; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceToBindSuccessfully(); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - - gameServiceController.onBootComplete(); - gameServiceController.notifyUserStarted(eligibleTargetUser(userId)); - - verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, gameServiceComponent); - - gameServiceController.notifyUserStopped(eligibleTargetUser(userId)); - - verify(mMockContext).unbindService(any()); - } - - @Test - public void testNonForegroundUserStopped() { - int userId1 = 123; - int userId2 = 456; - String gameServicePackageName = "game.service.package"; - String gameServiceComponent = "game.service.package.example.GameService"; - seedSystemGameServicePackageName(gameServicePackageName); - seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceResolveInfos(gameServicePackageName, userId2, ImmutableList.of( - resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true)))); - seedGameServiceToBindSuccessfully(); - - GameServiceController gameServiceController = - new GameServiceController(mMockContext); - InOrder inOrder = Mockito.inOrder(mMockContext); - - gameServiceController.onBootComplete(); - gameServiceController.notifyUserStarted(eligibleTargetUser(userId1)); - - inOrder.verify(mMockContext).bindServiceAsUser(any(), any(), anyInt(), userWithId(userId1)); - - gameServiceController.notifyNewForegroundUser(eligibleTargetUser(userId2)); - - inOrder.verify(mMockContext).unbindService(any()); - inOrder.verify(mMockContext).bindServiceAsUser(any(), any(), anyInt(), userWithId(userId2)); - - gameServiceController.notifyUserStopped(eligibleTargetUser(userId1)); - - inOrder.verify(mMockContext, never()).unbindService(any()); - } - - private void seedSystemGameServicePackageName(String gameServicePackageName) { - when(mMockContext.getResources()).thenReturn(mMockResources); - when(mMockResources.getString(com.android.internal.R.string.config_systemGameService)) - .thenReturn(gameServicePackageName); - } - - private void seedGameServiceResolveInfos(String gameServicePackageName, int userId, - List<ResolveInfo> resolveInfos) { - when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); - doReturn(resolveInfos) - .when(mMockPackageManager).queryIntentServicesAsUser( - argThat(intent -> - intent != null - && intent.getAction().equals(GameService.SERVICE_INTERFACE) - && intent.getPackage().equals(gameServicePackageName) - ), - eq(PackageManager.MATCH_SYSTEM_ONLY), - eq(userId)); - } - - private void seedGameServiceToBindSuccessfully() { - when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); - } - - private void verifyNoServiceBound() { - verify(mMockContext, never()).bindServiceAsUser(any(), any(), anyInt(), any()); - } - - private void verifyServiceBoundForUserAndComponent(int userId, String gameServicePackageName, - String gameServiceComponent) { - verify(mMockContext).bindServiceAsUser( - argThat(intent -> intent.getAction().equals(GameService.SERVICE_INTERFACE) - && intent.getComponent().getPackageName().equals(gameServicePackageName) - && intent.getComponent().getClassName().equals(gameServiceComponent)), - any(), - anyInt(), argThat(userInfo -> userInfo.getIdentifier() == userId)); - } - - private void verifyServiceNotBoundForUser(int userId) { - verify(mMockContext, never()).bindServiceAsUser( - any(), - any(), - anyInt(), userWithId(userId)); - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java new file mode 100644 index 000000000000..b6c706ed2730 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2021 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.app; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; + +import android.annotation.Nullable; +import android.app.IActivityTaskManager; +import android.app.ITaskStackListener; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.service.games.CreateGameSessionRequest; +import android.service.games.IGameService; +import android.service.games.IGameSession; +import android.service.games.IGameSessionService; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.infra.AndroidFuture; +import com.android.internal.util.ConcurrentUtils; +import com.android.internal.util.FunctionalUtils.ThrowingConsumer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + + +/** + * Unit tests for the {@link GameServiceProviderInstanceImpl}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public final class GameServiceProviderInstanceImplTest { + + private static final int USER_ID = 10; + private static final String APP_A_PACKAGE = "com.package.app.a"; + private static final ComponentName APP_A_MAIN_ACTIVITY = + new ComponentName(APP_A_PACKAGE, "com.package.app.a.MainActivity"); + + private static final String GAME_A_PACKAGE = "com.package.game.a"; + private static final ComponentName GAME_A_MAIN_ACTIVITY = + new ComponentName(GAME_A_PACKAGE, "com.package.game.a.MainActivity"); + + private MockitoSession mMockingSession; + private GameServiceProviderInstance mGameServiceProviderInstance; + @Mock + private IActivityTaskManager mMockActivityTaskManager; + @Mock + private IGameService mMockGameService; + @Mock + private IGameSessionService mMockGameSessionService; + private FakeGameClassifier mFakeGameClassifier; + private FakeServiceConnector<IGameService> mFakeGameServiceConnector; + private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector; + private ArrayList<ITaskStackListener> mTaskStackListeners; + private InOrder mInOrder; + + @Before + public void setUp() throws PackageManager.NameNotFoundException, RemoteException { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + + mInOrder = inOrder(mMockGameService, mMockGameSessionService); + + mFakeGameClassifier = new FakeGameClassifier(); + mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE); + + mFakeGameServiceConnector = new FakeServiceConnector<>(mMockGameService); + mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mMockGameSessionService); + + mTaskStackListeners = new ArrayList<>(); + doAnswer(invocation -> { + mTaskStackListeners.add(invocation.getArgument(0)); + return null; + }).when(mMockActivityTaskManager).registerTaskStackListener(any()); + + doAnswer(invocation -> { + mTaskStackListeners.remove(invocation.getArgument(0)); + return null; + }).when(mMockActivityTaskManager).unregisterTaskStackListener(any()); + + mGameServiceProviderInstance = new GameServiceProviderInstanceImpl( + new UserHandle(USER_ID), + ConcurrentUtils.DIRECT_EXECUTOR, + mFakeGameClassifier, + mMockActivityTaskManager, + mFakeGameServiceConnector, + mFakeGameSessionServiceConnector); + } + + @After + public void tearDown() { + mMockingSession.finishMocking(); + } + + @Test + public void start_startsGameSession() throws Exception { + mGameServiceProviderInstance.start(); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void start_multipleTimes_startsGameSessionOnce() throws Exception { + mGameServiceProviderInstance.start(); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void stop_neverStarted_doesNothing() throws Exception { + mGameServiceProviderInstance.stop(); + + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + mInOrder.verifyNoMoreInteractions(); + } + + @Test + public void startAndStop_startsAndStopsGameSession() throws Exception { + mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.stop(); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameService).disconnected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void startAndStop_multipleTimes_startsAndStopsGameSessionMultipleTimes() + throws Exception { + mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.stop(); + mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.stop(); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameService).disconnected(); + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameService).disconnected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(2); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void stop_stopMultipleTimes_stopsGameSessionOnce() throws Exception { + mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.stop(); + mGameServiceProviderInstance.stop(); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameService).disconnected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void gameTaskStarted_neverStarted_doesNothing() throws Exception { + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void gameTaskRemoved_neverStarted_doesNothing() throws Exception { + dispatchTaskRemoved(10); + + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void gameTaskStarted_afterStopped_doesNothing() throws Exception { + mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.stop(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameService).disconnected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void appTaskStarted_doesNothing() throws Exception { + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, APP_A_MAIN_ACTIVITY); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void taskStarted_nullComponentName_ignoresAndDoesNotCrash() throws Exception { + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, null); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + } + + @Test + public void gameTaskStarted_createsGameSession() throws Exception { + CreateGameSessionRequest createGameSessionRequest = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() + throws Exception { + CreateGameSessionRequest createGameSessionRequest = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + dispatchTaskRemoved(10); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void gameTaskRemoved_destroysGameSession() throws Exception { + CreateGameSessionRequest createGameSessionRequest = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + dispatchTaskRemoved(10); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any()); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void gameTaskStarted_multipleTimes_createsMultipleGameSessions() throws Exception { + CreateGameSessionRequest createGameSessionRequest10 = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest10); + + CreateGameSessionRequest createGameSessionRequest11 = + new CreateGameSessionRequest(11, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession11Future = + captureCreateGameSessionFuture(createGameSessionRequest11); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + + dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession11 = new IGameSessionStub(); + gameSession11Future.get().complete(gameSession11); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(gameSession11.mIsDestroyed).isFalse(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void gameTaskRemoved_afterMultipleCreated_destroysOnlyThatGameSession() + throws Exception { + CreateGameSessionRequest createGameSessionRequest10 = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest10); + + CreateGameSessionRequest createGameSessionRequest11 = + new CreateGameSessionRequest(11, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession11Future = + captureCreateGameSessionFuture(createGameSessionRequest11); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + + dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession11 = new IGameSessionStub(); + gameSession11Future.get().complete(gameSession11); + + dispatchTaskRemoved(10); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(gameSession11.mIsDestroyed).isFalse(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void allGameTasksRemoved_destroysAllGameSessions() throws Exception { + CreateGameSessionRequest createGameSessionRequest10 = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest10); + + CreateGameSessionRequest createGameSessionRequest11 = + new CreateGameSessionRequest(11, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession11Future = + captureCreateGameSessionFuture(createGameSessionRequest11); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + + dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession11 = new IGameSessionStub(); + gameSession11Future.get().complete(gameSession11); + + dispatchTaskRemoved(10); + dispatchTaskRemoved(11); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(gameSession11.mIsDestroyed).isTrue(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + @Test + public void gameTasksCreated_afterAllPreviousSessionsDestroyed_createsSession() + throws Exception { + CreateGameSessionRequest createGameSessionRequest10 = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest10); + + CreateGameSessionRequest createGameSessionRequest11 = + new CreateGameSessionRequest(11, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession11Future = + captureCreateGameSessionFuture(createGameSessionRequest11); + + CreateGameSessionRequest createGameSessionRequest12 = + new CreateGameSessionRequest(12, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> unusedGameSession12Future = + captureCreateGameSessionFuture(createGameSessionRequest12); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + + dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession11 = new IGameSessionStub(); + gameSession11Future.get().complete(gameSession11); + + dispatchTaskRemoved(10); + dispatchTaskRemoved(11); + + dispatchTaskCreated(12, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession12 = new IGameSessionStub(); + gameSession11Future.get().complete(gameSession12); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest12), any()); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(gameSession11.mIsDestroyed).isTrue(); + assertThat(gameSession12.mIsDestroyed).isFalse(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(2); + } + + @Test + public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception { + CreateGameSessionRequest createGameSessionRequest10 = + new CreateGameSessionRequest(10, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession10Future = + captureCreateGameSessionFuture(createGameSessionRequest10); + + CreateGameSessionRequest createGameSessionRequest11 = + new CreateGameSessionRequest(11, GAME_A_PACKAGE); + Supplier<AndroidFuture<IBinder>> gameSession11Future = + captureCreateGameSessionFuture(createGameSessionRequest11); + + mGameServiceProviderInstance.start(); + dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession10 = new IGameSessionStub(); + gameSession10Future.get().complete(gameSession10); + dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); + IGameSessionStub gameSession11 = new IGameSessionStub(); + gameSession11Future.get().complete(gameSession11); + mGameServiceProviderInstance.stop(); + + mInOrder.verify(mMockGameService).connected(); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); + mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); + mInOrder.verify(mMockGameService).disconnected(); + mInOrder.verifyNoMoreInteractions(); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(gameSession11.mIsDestroyed).isTrue(); + assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); + assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); + assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1); + } + + private Supplier<AndroidFuture<IBinder>> captureCreateGameSessionFuture( + CreateGameSessionRequest expectedCreateGameSessionRequest) throws Exception { + final AtomicReference<AndroidFuture<IBinder>> gameSessionFuture = new AtomicReference<>(); + doAnswer(invocation -> { + gameSessionFuture.set(invocation.getArgument(1)); + return null; + }).when(mMockGameSessionService).create(eq(expectedCreateGameSessionRequest), any()); + + return gameSessionFuture::get; + } + + private void dispatchTaskRemoved(int taskId) { + dispatchTaskChangeEvent(taskStackListener -> { + taskStackListener.onTaskRemoved(taskId); + }); + } + + private void dispatchTaskCreated(int taskId, @Nullable ComponentName componentName) { + dispatchTaskChangeEvent(taskStackListener -> { + taskStackListener.onTaskCreated(taskId, componentName); + }); + } + + private void dispatchTaskChangeEvent( + ThrowingConsumer<ITaskStackListener> taskStackListenerConsumer) { + for (ITaskStackListener taskStackListener : mTaskStackListeners) { + taskStackListenerConsumer.accept(taskStackListener); + } + } + + private static class IGameSessionStub extends IGameSession.Stub { + boolean mIsDestroyed = false; + + @Override + public void destroy() { + mIsDestroyed = true; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java new file mode 100644 index 000000000000..59d0970f5934 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2021 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.app; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.platform.test.annotations.Presubmit; +import android.service.games.GameService; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.SystemService; + +import com.google.common.collect.ImmutableList; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + + +/** + * Unit tests for the {@link GameServiceProviderSelectorImpl}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public final class GameServiceProviderSelectorImplTest { + + private static final UserHandle USER_HANDLE_10 = new UserHandle(10); + + private static final int GAME_SERVICE_META_DATA_RES_ID = 1337; + private static final String GAME_SERVICE_PACKAGE_NAME = "com.game.service.provider"; + private static final String GAME_SERVICE_CLASS_NAME = "com.game.service.provider.GameService"; + private static final ComponentName GAME_SERVICE_COMPONENT = + new ComponentName(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_CLASS_NAME); + + private static final int GAME_SERVICE_B_META_DATA_RES_ID = 1338; + private static final String GAME_SERVICE_B_CLASS_NAME = + "com.game.service.provider.GameServiceB"; + private static final ComponentName GAME_SERVICE_B_COMPONENT = + new ComponentName(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_B_CLASS_NAME); + private static final ServiceInfo GAME_SERVICE_B_WITH_OUT_META_DATA = + serviceInfo(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_B_CLASS_NAME); + private static final ServiceInfo GAME_SERVICE_B_SERVICE_INFO = + addGameServiceMetaData(GAME_SERVICE_B_WITH_OUT_META_DATA, + GAME_SERVICE_B_META_DATA_RES_ID); + + private static final String GAME_SESSION_SERVICE_CLASS_NAME = + "com.game.service.provider.GameSessionService"; + private static final ComponentName GAME_SESSION_SERVICE_COMPONENT = + new ComponentName(GAME_SERVICE_PACKAGE_NAME, GAME_SESSION_SERVICE_CLASS_NAME); + private static final ServiceInfo GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA = + serviceInfo(GAME_SERVICE_PACKAGE_NAME, GAME_SERVICE_CLASS_NAME); + private static final ServiceInfo GAME_SERVICE_SERVICE_INFO = + addGameServiceMetaData(GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA, + GAME_SERVICE_META_DATA_RES_ID); + + @Mock + private PackageManager mMockPackageManager; + private Resources mSpyResources; + private MockitoSession mMockingSession; + private GameServiceProviderSelector mGameServiceProviderSelector; + + @Before + public void setUp() throws PackageManager.NameNotFoundException { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + + mSpyResources = spy( + InstrumentationRegistry.getInstrumentation().getContext().getResources()); + + when(mMockPackageManager.getResourcesForApplication(anyString())) + .thenReturn(mSpyResources); + mGameServiceProviderSelector = new GameServiceProviderSelectorImpl( + mSpyResources, + mMockPackageManager); + } + + @After + public void tearDown() { + mMockingSession.finishMocking(); + } + + @Test + public void get_nullUser_returnsNull() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(null); + + assertThat(gameServiceProviderConfiguration).isNull(); + } + + @Test + public void get_managedUser_returnsNull() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(managedTargetUser(USER_HANDLE_10)); + + assertThat(gameServiceProviderConfiguration).isNull(); + } + + @Test + public void get_noSystemGameService_returnsNull() + throws Exception { + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + assertThat(gameServiceProviderConfiguration).isNull(); + } + + @Test + public void get_noGameServiceProvidersAvailable_returnsNull() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + assertThat(gameServiceProviderConfiguration).isNull(); + } + + @Test + public void get_gameServiceProviderHasNoMetaData_returnsNull() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + assertThat(gameServiceProviderConfiguration).isNull(); + } + + @Test + public void get_gameSessionServiceDoesNotExist_returnsNull() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfoNotFound(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + assertThat(gameServiceProviderConfiguration).isNull(); + } + + @Test + public void get_metaDataWrongFirstTag_returnsNull() throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_wrong_first_tag.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + assertThat(gameServiceProviderConfiguration).isNull(); + } + + @Test + public void get_validGameServiceProviderAvailable_returnsGameServiceProvider() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + GameServiceProviderConfiguration expectedGameServiceProviderConfiguration = + new GameServiceProviderConfiguration(USER_HANDLE_10, + GAME_SERVICE_COMPONENT, + GAME_SESSION_SERVICE_COMPONENT); + assertThat(gameServiceProviderConfiguration).isEqualTo( + expectedGameServiceProviderConfiguration); + } + + @Test + public void get_multipleGameServiceProvidersAllValid_returnsFirstValidGameServiceProvider() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_B_SERVICE_INFO), resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_B_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + GameServiceProviderConfiguration expectedGameServiceProviderConfiguration = + new GameServiceProviderConfiguration(USER_HANDLE_10, + GAME_SERVICE_B_COMPONENT, + GAME_SESSION_SERVICE_COMPONENT); + assertThat(gameServiceProviderConfiguration).isEqualTo( + expectedGameServiceProviderConfiguration); + } + + @Test + public void get_multipleGameServiceProvidersSomeInvalid_returnsFirstValidGameServiceProvider() + throws Exception { + seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); + + seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, + resolveInfo(GAME_SERVICE_B_SERVICE_INFO), resolveInfo(GAME_SERVICE_SERVICE_INFO)); + seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); + seedGameServiceMetaDataFromFile(GAME_SERVICE_PACKAGE_NAME, + GAME_SERVICE_META_DATA_RES_ID, + "res/xml/game_service_metadata_valid.xml"); + + GameServiceProviderConfiguration gameServiceProviderConfiguration = + mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10)); + + GameServiceProviderConfiguration expectedGameServiceProviderConfiguration = + new GameServiceProviderConfiguration(USER_HANDLE_10, + GAME_SERVICE_COMPONENT, + GAME_SESSION_SERVICE_COMPONENT); + assertThat(gameServiceProviderConfiguration).isEqualTo( + expectedGameServiceProviderConfiguration); + } + + private void seedSystemGameServicePackageName(String gameServicePackageName) { + when(mSpyResources.getString(com.android.internal.R.string.config_systemGameService)) + .thenReturn(gameServicePackageName); + } + + private void seedGameServiceResolveInfos( + String gameServicePackageName, + UserHandle userHandle, + ResolveInfo... resolveInfos) { + doReturn(ImmutableList.copyOf(resolveInfos)) + .when(mMockPackageManager).queryIntentServicesAsUser( + argThat(intent -> + intent != null + && intent.getAction().equals( + GameService.ACTION_GAME_SERVICE) + && intent.getPackage().equals(gameServicePackageName) + ), + anyInt(), + eq(userHandle.getIdentifier())); + } + + private void seedServiceServiceInfo(ComponentName componentName) throws Exception { + when(mMockPackageManager.getServiceInfo(eq(componentName), anyInt())) + .thenReturn( + serviceInfo(componentName.getPackageName(), componentName.getClassName())); + } + + private void seedServiceServiceInfoNotFound(ComponentName componentName) throws Exception { + when(mMockPackageManager.getServiceInfo(eq(componentName), anyInt())) + .thenThrow(new PackageManager.NameNotFoundException()); + } + + private void seedGameServiceMetaDataFromFile(String packageName, int resId, String fileName) + throws Exception { + + AssetManager assetManager = + InstrumentationRegistry.getInstrumentation().getContext().getAssets(); + XmlResourceParser xmlResourceParser = + assetManager.openXmlResourceParser(fileName); + + when(mMockPackageManager.getXml(eq(packageName), eq(resId), any())) + .thenReturn(xmlResourceParser); + } + + private static UserInfo eligibleUserInfo(int uid) { + return new UserInfo(uid, "", "", UserInfo.FLAG_FULL); + } + + private static UserInfo managedUserInfo(int uid) { + UserInfo userInfo = eligibleUserInfo(uid); + userInfo.userType = UserManager.USER_TYPE_PROFILE_MANAGED; + return userInfo; + } + + private static ResolveInfo resolveInfo(ServiceInfo serviceInfo) { + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + return resolveInfo; + } + + private static ServiceInfo serviceInfo(String packageName, String name) { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = packageName; + applicationInfo.enabled = true; + + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.applicationInfo = applicationInfo; + serviceInfo.packageName = packageName; + serviceInfo.name = name; + serviceInfo.enabled = true; + + return serviceInfo; + } + + private static ServiceInfo addGameServiceMetaData(ServiceInfo serviceInfo, int resId) { + if (serviceInfo.metaData == null) { + serviceInfo.metaData = new Bundle(); + } + serviceInfo.metaData.putInt(GameService.SERVICE_META_DATA, resId); + + return serviceInfo; + } + + private static SystemService.TargetUser managedTargetUser(UserHandle userHandle) { + return new SystemService.TargetUser(managedUserInfo(userHandle.getIdentifier())); + } + + private static SystemService.TargetUser eligibleTargetUser(UserHandle userHandle) { + return new SystemService.TargetUser(eligibleUserInfo(userHandle.getIdentifier())); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt new file mode 100644 index 000000000000..edbfecc41b04 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 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.pm + +import android.os.Build +import com.android.server.testutils.any +import com.android.server.testutils.spy +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.eq +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import kotlin.test.assertFailsWith + +@RunWith(JUnit4::class) +class PackageFreezerTest { + + companion object { + const val TEST_PACKAGE = "com.android.test.package" + const val TEST_REASON = "test reason" + const val TEST_USER_ID = 0 + } + + @Rule + @JvmField + val rule = MockSystemRule() + + lateinit var pms: PackageManagerService + + private fun createPackageManagerService(vararg stageExistingPackages: String): + PackageManagerService { + stageExistingPackages.forEach { + rule.system().stageScanExistingPackage(it, 1L, + rule.system().dataAppDirectory) + } + var pms = PackageManagerService(rule.mocks().injector, + false /*coreOnly*/, + false /*factoryTest*/, + MockSystem.DEFAULT_VERSION_INFO.fingerprint, + false /*isEngBuild*/, + false /*isUserDebugBuild*/, + Build.VERSION_CODES.CUR_DEVELOPMENT, + Build.VERSION.INCREMENTAL, + false /*snapshotEnabled*/) + rule.system().validateFinalState() + return pms + } + + private fun frozenMessage(packageName: String) = "Package $packageName is currently frozen!" + + private fun <T : Throwable> assertThrowContainsMessage( + exceptionClass: kotlin.reflect.KClass<T>, + message: String, + block: () -> Unit + ) { + assertThat(assertFailsWith(exceptionClass, block).message).contains(message) + } + + @Before + @Throws(Exception::class) + fun setup() { + rule.system().stageNominalSystemState() + pms = spy(createPackageManagerService(TEST_PACKAGE)) + whenever(pms.killApplication(any(), any(), any(), any())) + } + + @Test + fun freezePackage() { + val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms) + verify(pms, times(1)) + .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON)) + + assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) { + pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + } + + freezer.close() + pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + } + + @Test + fun freezePackage_twice() { + val freezer1 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms) + val freezer2 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms) + verify(pms, times(2)) + .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON)) + + assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) { + pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + } + + freezer1.close() + assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) { + pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + } + + freezer2.close() + pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + } + + @Test + fun freezePackage_withoutClosing() { + var freezer: PackageFreezer? = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms) + verify(pms, times(1)) + .killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON)) + + assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) { + pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + } + + freezer = null + System.gc() + System.runFinalization() + + pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/pm/TEST_MAPPING new file mode 100644 index 000000000000..13e255fe4ab8 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "presubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + { + "include-filter": "com.android.server.pm" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 80f272905143..e756124e2e99 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -97,6 +97,10 @@ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> + <queries> + <package android:name="com.android.servicestests.apps.suspendtestapp" /> + </queries> + <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="26"/> diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index d926dcba54a3..b2854ceb1017 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -36,6 +36,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.CompatibilityOverridesByPackageConfig; +import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig; import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; import org.junit.Before; @@ -303,6 +305,51 @@ public class CompatConfigTest { assertThat(compatConfig.isChangeEnabled(unknownChangeId, applicationInfo)).isTrue(); } + @Test + public void testInstallerCanAddOverridesForMultiplePackages() throws Exception { + final String packageName1 = "com.some.package1"; + final String packageName2 = "com.some.package2"; + final long disabledChangeId1 = 1234L; + final long disabledChangeId2 = 1235L; + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledOverridableChangeWithId(disabledChangeId1) + .addDisabledOverridableChangeWithId(disabledChangeId2) + .build(); + ApplicationInfo applicationInfo1 = ApplicationInfoBuilder.create() + .withPackageName(packageName1) + .build(); + ApplicationInfo applicationInfo2 = ApplicationInfoBuilder.create() + .withPackageName(packageName2) + .build(); + PackageManager packageManager = mock(PackageManager.class); + when(mContext.getPackageManager()).thenReturn(packageManager); + when(packageManager.getApplicationInfo(eq(packageName1), anyInt())) + .thenReturn(applicationInfo1); + when(packageManager.getApplicationInfo(eq(packageName2), anyInt())) + .thenReturn(applicationInfo2); + + // Force the validator to prevent overriding non-overridable changes by using a user build. + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + Map<Long, PackageOverride> overrides1 = new HashMap<>(); + overrides1.put(disabledChangeId1, new PackageOverride.Builder().setEnabled(true).build()); + Map<Long, PackageOverride> overrides2 = new HashMap<>(); + overrides2.put(disabledChangeId1, new PackageOverride.Builder().setEnabled(true).build()); + overrides2.put(disabledChangeId2, new PackageOverride.Builder().setEnabled(true).build()); + Map<String, CompatibilityOverrideConfig> packageNameToOverrides = new HashMap<>(); + packageNameToOverrides.put(packageName1, new CompatibilityOverrideConfig(overrides1)); + packageNameToOverrides.put(packageName2, new CompatibilityOverrideConfig(overrides2)); + CompatibilityOverridesByPackageConfig config = new CompatibilityOverridesByPackageConfig( + packageNameToOverrides); + + compatConfig.addAllPackageOverrides(config, /* skipUnknownChangeIds */ true); + + assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isTrue(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isFalse(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo2)).isTrue(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo2)).isTrue(); + } + @Test public void testPreventInstallerSetNonOverridable() throws Exception { @@ -641,6 +688,73 @@ public class CompatConfigTest { } @Test + public void testInstallerCanRemoveOverridesForMultiplePackages() throws Exception { + final String packageName1 = "com.some.package1"; + final String packageName2 = "com.some.package2"; + final long disabledChangeId1 = 1234L; + final long disabledChangeId2 = 1235L; + final long enabledChangeId = 1236L; + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledOverridableChangeWithId(disabledChangeId1) + .addDisabledOverridableChangeWithId(disabledChangeId2) + .addEnabledOverridableChangeWithId(enabledChangeId) + .build(); + ApplicationInfo applicationInfo1 = ApplicationInfoBuilder.create() + .withPackageName(packageName1) + .build(); + ApplicationInfo applicationInfo2 = ApplicationInfoBuilder.create() + .withPackageName(packageName2) + .build(); + PackageManager packageManager = mock(PackageManager.class); + when(mContext.getPackageManager()).thenReturn(packageManager); + when(packageManager.getApplicationInfo(eq(packageName1), anyInt())) + .thenReturn(applicationInfo1); + when(packageManager.getApplicationInfo(eq(packageName2), anyInt())) + .thenReturn(applicationInfo2); + + assertThat(compatConfig.addOverride(disabledChangeId1, packageName1, true)).isTrue(); + assertThat(compatConfig.addOverride(disabledChangeId2, packageName1, true)).isTrue(); + assertThat(compatConfig.addOverride(enabledChangeId, packageName1, false)).isTrue(); + assertThat(compatConfig.addOverride(disabledChangeId1, packageName2, true)).isTrue(); + assertThat(compatConfig.addOverride(disabledChangeId2, packageName2, true)).isTrue(); + assertThat(compatConfig.addOverride(enabledChangeId, packageName2, false)).isTrue(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isTrue(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isTrue(); + assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo1)).isFalse(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isTrue(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isTrue(); + assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo1)).isFalse(); + + // Force the validator to prevent overriding non-overridable changes by using a user build. + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + Set<Long> overridesToRemove1 = new HashSet<>(); + overridesToRemove1.add(disabledChangeId1); + overridesToRemove1.add(enabledChangeId); + Set<Long> overridesToRemove2 = new HashSet<>(); + overridesToRemove2.add(disabledChangeId1); + overridesToRemove2.add(disabledChangeId2); + Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove = + new HashMap<>(); + packageNameToOverridesToRemove.put(packageName1, + new CompatibilityOverridesToRemoveConfig(overridesToRemove1)); + packageNameToOverridesToRemove.put(packageName2, + new CompatibilityOverridesToRemoveConfig(overridesToRemove2)); + CompatibilityOverridesToRemoveByPackageConfig config = + new CompatibilityOverridesToRemoveByPackageConfig(packageNameToOverridesToRemove); + + compatConfig.removeAllPackageOverrides(config); + + assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo1)).isFalse(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo1)).isTrue(); + assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo1)).isTrue(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo2)).isFalse(); + assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo2)).isFalse(); + assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo2)).isFalse(); + } + + @Test public void testPreventInstallerRemoveNonOverridable() throws Exception { final long disabledChangeId1 = 1234L; final long disabledChangeId2 = 1235L; diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java index 2290ef79da78..398148ff4d3b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java @@ -25,23 +25,16 @@ import static android.app.AppOpsManager.opToName; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; import android.app.AppGlobals; -import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; -import android.content.res.Resources; import android.os.BaseBundle; import android.os.Bundle; import android.os.Handler; @@ -50,13 +43,6 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject2; -import android.support.test.uiautomator.Until; -import android.util.Log; -import android.view.IWindowManager; -import android.view.WindowManagerGlobal; import androidx.test.InstrumentationRegistry; import androidx.test.filters.FlakyTest; @@ -65,7 +51,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; -import com.android.servicestests.apps.suspendtestapp.SuspendTestActivity; import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver; import org.junit.After; @@ -76,7 +61,6 @@ import org.junit.runner.RunWith; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -84,8 +68,6 @@ import java.util.concurrent.atomic.AtomicReference; @LargeTest @FlakyTest public class SuspendPackagesTest { - private static final String TAG = SuspendPackagesTest.class.getSimpleName(); - private static final String TEST_APP_LABEL = "Suspend Test App"; private static final String TEST_APP_PACKAGE_NAME = SuspendTestReceiver.PACKAGE_NAME; private static final String[] PACKAGES_TO_SUSPEND = new String[]{TEST_APP_PACKAGE_NAME}; @@ -105,75 +87,11 @@ public class SuspendPackagesTest { public static final String EXTRA_RECEIVED_PACKAGE_NAME = SuspendPackagesTest.INSTRUMENTATION_PACKAGE + ".extra.RECEIVED_PACKAGE_NAME"; - private Context mContext; private PackageManager mPackageManager; private LauncherApps mLauncherApps; private Handler mReceiverHandler; - private AppCommunicationReceiver mAppCommsReceiver; private StubbedCallback mTestCallback; - private UiDevice mUiDevice; - private ComponentName mDeviceAdminComponent; - private boolean mPoSet; - private boolean mDoSet; - - private static final class AppCommunicationReceiver extends BroadcastReceiver { - private Context context; - private boolean registered; - private SynchronousQueue<Intent> intentQueue = new SynchronousQueue<>(); - - AppCommunicationReceiver(Context context) { - this.context = context; - } - - void register(Handler handler, String... actions) { - registered = true; - final IntentFilter intentFilter = new IntentFilter(); - for (String action : actions) { - intentFilter.addAction(action); - } - context.registerReceiver(this, intentFilter, null, handler); - } - - void unregister() { - if (registered) { - context.unregisterReceiver(this); - } - } - - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "AppCommunicationReceiver#onReceive: " + intent.getAction()); - try { - intentQueue.offer(intent, 5, TimeUnit.SECONDS); - } catch (InterruptedException ie) { - throw new RuntimeException("Receiver thread interrupted", ie); - } - } - - Intent pollForIntent(long secondsToWait) { - if (!registered) { - throw new IllegalStateException("Receiver not registered"); - } - final Intent intent; - try { - intent = intentQueue.poll(secondsToWait, TimeUnit.SECONDS); - } catch (InterruptedException ie) { - throw new RuntimeException("Interrupted while waiting for app broadcast", ie); - } - return intent; - } - - void drainPendingBroadcasts() { - while (pollForIntent(5) != null) ; - } - - Intent receiveIntentFromApp() { - final Intent intentReceived = pollForIntent(5); - assertNotNull("No intent received from app within 5 seconds", intentReceived); - return intentReceived; - } - } @Before public void setUp() { @@ -181,9 +99,6 @@ public class SuspendPackagesTest { mPackageManager = mContext.getPackageManager(); mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE); mReceiverHandler = new Handler(Looper.getMainLooper()); - mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - mDeviceAdminComponent = new ComponentName(mContext, - "com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"); IPackageManager ipm = AppGlobals.getPackageManager(); try { // Otherwise implicit broadcasts will not be delivered. @@ -192,31 +107,6 @@ public class SuspendPackagesTest { e.rethrowAsRuntimeException(); } unsuspendTestPackage(); - mAppCommsReceiver = new AppCommunicationReceiver(mContext); - } - - /** - * Care should be taken when used with {@link #mAppCommsReceiver} in the same test as both use - * the same handler. - */ - private Bundle requestAppAction(String action) throws InterruptedException { - final AtomicReference<Bundle> result = new AtomicReference<>(); - final CountDownLatch receiverLatch = new CountDownLatch(1); - final ComponentName testReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME, - SuspendTestReceiver.class.getCanonicalName()); - final Intent broadcastIntent = new Intent(action) - .setComponent(testReceiverComponent) - .setFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendOrderedBroadcast(broadcastIntent, null, new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - result.set(getResultExtras(true)); - receiverLatch.countDown(); - } - }, mReceiverHandler, 0, null, null); - - assertTrue("Test receiver timed out ", receiverLatch.await(5, TimeUnit.SECONDS)); - return result.get(); } private PersistableBundle getExtras(String keyPrefix, long lval, String sval, double dval) { @@ -240,14 +130,6 @@ public class SuspendPackagesTest { assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0); } - private void startTestAppActivity() { - final Intent testActivity = new Intent() - .setComponent(new ComponentName(TEST_APP_PACKAGE_NAME, - SuspendTestActivity.class.getCanonicalName())) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(testActivity); - } - private static boolean areSameExtras(BaseBundle expected, BaseBundle received) { if (expected != null) { expected.get(""); // hack to unparcel the bundles. @@ -265,93 +147,6 @@ public class SuspendPackagesTest { } @Test - public void testIsPackageSuspended() throws Exception { - suspendTestPackage(null, null, null); - assertTrue("isPackageSuspended is false", - mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME)); - } - - @Test - public void testSuspendedStateFromApp() throws Exception { - Bundle resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE); - assertFalse(resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED, true)); - assertNull(resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS)); - - final PersistableBundle appExtras = getExtras("testSuspendedStateFromApp", 20, "20", 0.2); - suspendTestPackage(appExtras, null, null); - - resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE); - assertTrue("resultFromApp:suspended is false", - resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED)); - final Bundle receivedAppExtras = - resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS); - assertSameExtras("Received app extras different to the ones supplied", - appExtras, receivedAppExtras); - } - - @Test - public void testMyPackageSuspendedUnsuspended() { - mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED, - ACTION_REPORT_MY_PACKAGE_UNSUSPENDED); - mAppCommsReceiver.drainPendingBroadcasts(); - final PersistableBundle appExtras = getExtras("testMyPackageSuspendBroadcasts", 1, "1", .1); - suspendTestPackage(appExtras, null, null); - Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals("MY_PACKAGE_SUSPENDED delivery not reported", - ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction()); - assertSameExtras("Received app extras different to the ones supplied", appExtras, - intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS)); - unsuspendTestPackage(); - intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals("MY_PACKAGE_UNSUSPENDED delivery not reported", - ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction()); - } - - @Test - public void testUpdatingAppExtras() { - mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED); - final PersistableBundle extras1 = getExtras("testMyPackageSuspendedOnChangingExtras", 1, - "1", 0.1); - suspendTestPackage(extras1, null, null); - Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals("MY_PACKAGE_SUSPENDED delivery not reported", - ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction()); - assertSameExtras("Received app extras different to the ones supplied", extras1, - intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS)); - final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2, - "2", 0.2); - suspendTestPackage(extras2, null, null); - intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals("MY_PACKAGE_SUSPENDED delivery not reported", - ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction()); - assertSameExtras("Received app extras different to the updated extras", extras2, - intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS)); - } - - @Test - public void testCannotSuspendSelf() { - final String[] unchangedPkgs = mPackageManager.setPackagesSuspended( - new String[]{mContext.getOpPackageName()}, true, null, null, - (SuspendDialogInfo) null); - assertTrue(unchangedPkgs.length == 1); - assertEquals(mContext.getOpPackageName(), unchangedPkgs[0]); - } - - @Test - public void testActivityStoppedOnSuspend() { - mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_TEST_ACTIVITY_STARTED, - ACTION_REPORT_TEST_ACTIVITY_STOPPED); - startTestAppActivity(); - Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals("Test activity start not reported", - ACTION_REPORT_TEST_ACTIVITY_STARTED, intentFromApp.getAction()); - suspendTestPackage(null, null, null); - intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals("Test activity stop not reported on suspending the test app", - ACTION_REPORT_TEST_ACTIVITY_STOPPED, intentFromApp.getAction()); - } - - @Test public void testGetLauncherExtrasNonNull() { final Bundle extrasWhenUnsuspended = mLauncherApps.getSuspendedPackageLauncherExtras( TEST_APP_PACKAGE_NAME, mContext.getUser()); @@ -383,14 +178,15 @@ public class SuspendPackagesTest { public void testOnPackagesSuspendedNewAndOld() throws InterruptedException { final PersistableBundle suppliedExtras = getExtras( "testOnPackagesSuspendedNewAndOld", 2, "2", 0.2); - final AtomicReference<String> overridingBothCallbackResult = new AtomicReference<>(""); - final CountDownLatch twoCallbackLatch = new CountDownLatch(2); + final AtomicReference<String> error = new AtomicReference<>(""); + final CountDownLatch rightCallbackLatch = new CountDownLatch(1); + final CountDownLatch wrongCallbackLatch = new CountDownLatch(1); mTestCallback = new StubbedCallback() { @Override public void onPackagesSuspended(String[] packageNames, UserHandle user) { - overridingBothCallbackResult.set(overridingBothCallbackResult.get() + error.set(error.get() + "Old callback called even when the new one is overriden. "); - twoCallbackLatch.countDown(); + wrongCallbackLatch.countDown(); } @Override @@ -411,17 +207,16 @@ public class SuspendPackagesTest { errorString.append("Unexpected launcherExtras, supplied: " + suppliedExtras + ", received: " + launcherExtras + ". "); } - overridingBothCallbackResult.set(overridingBothCallbackResult.get() + error.set(error.get() + errorString.toString()); - twoCallbackLatch.countDown(); + rightCallbackLatch.countDown(); } }; mLauncherApps.registerCallback(mTestCallback, mReceiverHandler); suspendTestPackage(null, suppliedExtras, null); - assertFalse("Both callbacks were invoked", twoCallbackLatch.await(5, TimeUnit.SECONDS)); - twoCallbackLatch.countDown(); - assertTrue("No callback was invoked", twoCallbackLatch.await(2, TimeUnit.SECONDS)); - final String result = overridingBothCallbackResult.get(); + assertFalse("Wrong callback was invoked", wrongCallbackLatch.await(5, TimeUnit.SECONDS)); + assertTrue("Right callback wasn't invoked", rightCallbackLatch.await(2, TimeUnit.SECONDS)); + final String result = error.get(); assertTrue("Callbacks did not complete as expected: " + result, result.isEmpty()); } @@ -457,103 +252,6 @@ public class SuspendPackagesTest { assertTrue("Callback did not complete as expected: " + result, result.isEmpty()); } - private void turnScreenOn() throws Exception { - if (!mUiDevice.isScreenOn()) { - mUiDevice.wakeUp(); - } - final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - wm.dismissKeyguard(null, null); - } - - @Test - public void testInterceptorActivity() throws Exception { - turnScreenOn(); - mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED, - ACTION_REPORT_TEST_ACTIVITY_STARTED); - final String testMessage = "This is a test message to report suspension of %1$s"; - suspendTestPackage(null, null, - new SuspendDialogInfo.Builder().setMessage(testMessage).build()); - startTestAppActivity(); - assertNull("No broadcast was expected from app", mAppCommsReceiver.pollForIntent(2)); - assertNotNull("Given dialog message not shown", mUiDevice.wait( - Until.findObject(By.text(String.format(testMessage, TEST_APP_LABEL))), 5000)); - final String buttonText = mContext.getResources().getString(Resources.getSystem() - .getIdentifier("app_suspended_more_details", "string", "android")); - final UiObject2 moreDetailsButton = mUiDevice.findObject( - By.clickable(true).text(buttonText)); - assertNotNull(buttonText + " button not shown", moreDetailsButton); - moreDetailsButton.click(); - final Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals(buttonText + " activity start not reported", - ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED, intentFromApp.getAction()); - final String receivedPackageName = intentFromApp.getStringExtra( - EXTRA_RECEIVED_PACKAGE_NAME); - assertEquals("Wrong package name received by " + buttonText + " activity", - TEST_APP_PACKAGE_NAME, receivedPackageName); - } - - private boolean setProfileOwner() throws IOException { - final String result = mUiDevice.executeShellCommand("dpm set-profile-owner --user cur " - + mDeviceAdminComponent.flattenToString()); - return mPoSet = result.trim().startsWith("Success"); - } - - private boolean setDeviceOwner() throws IOException { - final String result = mUiDevice.executeShellCommand("dpm set-device-owner --user cur " - + mDeviceAdminComponent.flattenToString()); - return mDoSet = result.trim().startsWith("Success"); - } - - private void removeProfileOrDeviceOwner() throws IOException { - if (mPoSet || mDoSet) { - mUiDevice.executeShellCommand("dpm remove-active-admin --user cur " - + mDeviceAdminComponent.flattenToString()); - mPoSet = mDoSet = false; - } - } - - @Test - public void testCanSuspendWhenProfileOwner() throws IOException { - assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)); - assertTrue("Profile-owner could not be set", setProfileOwner()); - suspendTestPackage(null, null, null); - } - - @Test - public void testCanSuspendWhenDeviceOwner() throws IOException { - assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)); - assertTrue("Device-owner could not be set", setDeviceOwner()); - suspendTestPackage(null, null, null); - } - - @Test - public void testPackageUnsuspendedOnAddingDeviceOwner() throws IOException { - assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)); - mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, - ACTION_REPORT_MY_PACKAGE_SUSPENDED); - mAppCommsReceiver.drainPendingBroadcasts(); - suspendTestPackage(null, null, null); - Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction()); - assertTrue("Device-owner could not be set", setDeviceOwner()); - intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction()); - } - - @Test - public void testPackageUnsuspendedOnAddingProfileOwner() throws IOException { - assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)); - mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, - ACTION_REPORT_MY_PACKAGE_SUSPENDED); - mAppCommsReceiver.drainPendingBroadcasts(); - suspendTestPackage(null, null, null); - Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals(ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction()); - assertTrue("Profile-owner could not be set", setProfileOwner()); - intentFromApp = mAppCommsReceiver.receiveIntentFromApp(); - assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction()); - } - @Test public void testCameraBlockedOnSuspend() throws Exception { assertOpBlockedOnSuspend(OP_CAMERA); @@ -596,13 +294,9 @@ public class SuspendPackagesTest { @After public void tearDown() throws IOException { - mAppCommsReceiver.unregister(); if (mTestCallback != null) { mLauncherApps.unregisterCallback(mTestCallback); } - removeProfileOrDeviceOwner(); - mContext.sendBroadcast(new Intent(ACTION_FINISH_TEST_ACTIVITY) - .setPackage(TEST_APP_PACKAGE_NAME)); } private static abstract class StubbedCallback extends LauncherApps.Callback { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 62a0dd4e7c17..419dda57c568 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -62,6 +62,7 @@ import static android.service.notification.NotificationListenerService.FLAG_FILT import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; +import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.google.common.truth.Truth.assertThat; @@ -5482,6 +5483,39 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testRateLimitedToasts_windowsRemoved() throws Exception { + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + setToastRateIsWithinQuota(false); // rate limit reached + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); + setAppInForegroundForToasts(mUid, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(false); + + Binder token = new Binder(); + INotificationManager nmService = (INotificationManager) mService.mService; + + nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null); + + // window token was added when enqueued + ArgumentCaptor<Binder> binderCaptor = + ArgumentCaptor.forClass(Binder.class); + verify(mWindowManagerInternal).addWindowToken(binderCaptor.capture(), + eq(TYPE_TOAST), anyInt(), eq(null)); + + // but never shown + verify(mStatusBar, times(0)) + .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any()); + + // and removed when rate limited + verify(mWindowManagerInternal) + .removeWindowToken(eq(binderCaptor.getValue()), eq(true), anyInt()); + } + + @Test public void backgroundSystemCustomToast_callsSetProcessImportantAsForegroundForToast() throws Exception { final String testPackage = "testPackageName"; diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index dc0e02800bb2..2ef59f6ac5c9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1422,18 +1422,16 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(config90.orientation, app.getConfiguration().orientation); assertEquals(config90.windowConfiguration.getBounds(), app.getBounds()); - // Make wallaper laid out with the fixed rotation transform. + // Associate wallpaper with the fixed rotation transform. final WindowToken wallpaperToken = mWallpaperWindow.mToken; wallpaperToken.linkFixedRotationTransform(app); - mWallpaperWindow.mLayoutNeeded = true; - performLayout(mDisplayContent); // Force the negative offset to verify it can be updated. mWallpaperWindow.mXOffset = mWallpaperWindow.mYOffset = -1; assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow, false /* sync */)); - assertThat(mWallpaperWindow.mXOffset).isGreaterThan(-1); - assertThat(mWallpaperWindow.mYOffset).isGreaterThan(-1); + assertThat(mWallpaperWindow.mXOffset).isNotEqualTo(-1); + assertThat(mWallpaperWindow.mYOffset).isNotEqualTo(-1); // The wallpaper need to animate with transformed position, so its surface position should // not be reset. diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 8da8596f47f9..69700052ce74 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -189,11 +189,6 @@ public class DisplayPolicyTests extends WindowTestsBase { assertEquals(0, displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS, null)); - // Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS. - assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, dimming)); - assertEquals(0, displayPolicy.updateLightNavigationBarLw( - APPEARANCE_LIGHT_NAVIGATION_BARS, dimming)); - // Control window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag. assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar)); assertEquals(0, displayPolicy.updateLightNavigationBarLw( diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 4220fd78c46f..2fb67d79ccee 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -175,10 +175,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, // which need debouncing. - private static final int DEVICE_STATE_UPDATE_DELAY = 3000; - - // Delay for debouncing USB disconnects on Type-C ports in host mode - private static final int HOST_STATE_UPDATE_DELAY = 1000; + private static final int UPDATE_DELAY = 1000; // Timeout for entering USB request mode. // Request is cancelled if host does not configure device within 10 seconds. @@ -648,7 +645,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser msg.arg1 = connected; msg.arg2 = configured; // debounce disconnects to avoid problems bringing up USB tethering - sendMessageDelayed(msg, (connected == 0) ? DEVICE_STATE_UPDATE_DELAY : 0); + sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0); } public void updateHostState(UsbPort port, UsbPortStatus status) { @@ -663,7 +660,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser removeMessages(MSG_UPDATE_PORT_STATE); Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args); // debounce rapid transitions of connect/disconnect on type-c ports - sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY); + sendMessageDelayed(msg, UPDATE_DELAY); } private void setAdbEnabled(boolean enable) { diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java index e2d62f868d52..6d9b3210ea70 100644 --- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java +++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java @@ -17,7 +17,6 @@ package com.google.android.mms.util; -import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.ContentValues; @@ -25,49 +24,15 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; -import android.os.Build; import android.util.Log; -import android.widget.Toast; public final class SqliteWrapper { private static final String TAG = "SqliteWrapper"; - private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE - = "unable to open database file"; private SqliteWrapper() { // Forbidden being instantiated. } - // FIXME: It looks like outInfo.lowMemory does not work well as we expected. - // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false. - private static boolean isLowMemory(Context context) { - if (null == context) { - return false; - } - - ActivityManager am = (ActivityManager) - context.getSystemService(Context.ACTIVITY_SERVICE); - ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo(); - am.getMemoryInfo(outInfo); - - return outInfo.lowMemory; - } - - // FIXME: need to optimize this method. - private static boolean isLowMemory(SQLiteException e) { - return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE); - } - - @UnsupportedAppUsage - public static void checkSQLiteException(Context context, SQLiteException e) { - if (isLowMemory(e)) { - Toast.makeText(context, com.android.internal.R.string.low_memory, - Toast.LENGTH_SHORT).show(); - } else { - throw e; - } - } - @UnsupportedAppUsage public static Cursor query(Context context, ContentResolver resolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { @@ -75,21 +40,10 @@ public final class SqliteWrapper { return resolver.query(uri, projection, selection, selectionArgs, sortOrder); } catch (SQLiteException e) { Log.e(TAG, "Catch a SQLiteException when query: ", e); - checkSQLiteException(context, e); return null; } } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static boolean requery(Context context, Cursor cursor) { - try { - return cursor.requery(); - } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when requery: ", e); - checkSQLiteException(context, e); - return false; - } - } @UnsupportedAppUsage public static int update(Context context, ContentResolver resolver, Uri uri, ContentValues values, String where, String[] selectionArgs) { @@ -97,7 +51,6 @@ public final class SqliteWrapper { return resolver.update(uri, values, where, selectionArgs); } catch (SQLiteException e) { Log.e(TAG, "Catch a SQLiteException when update: ", e); - checkSQLiteException(context, e); return -1; } } @@ -109,7 +62,6 @@ public final class SqliteWrapper { return resolver.delete(uri, where, selectionArgs); } catch (SQLiteException e) { Log.e(TAG, "Catch a SQLiteException when delete: ", e); - checkSQLiteException(context, e); return -1; } } @@ -121,7 +73,6 @@ public final class SqliteWrapper { return resolver.insert(uri, values); } catch (SQLiteException e) { Log.e(TAG, "Catch a SQLiteException when insert: ", e); - checkSQLiteException(context, e); return null; } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d835ea4a7254..2a600d5afb88 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6145,6 +6145,7 @@ public class TelephonyManager { DATA_CONNECTED, DATA_SUSPENDED, DATA_DISCONNECTING, + DATA_HANDOVER_IN_PROGRESS, }) @Retention(RetentionPolicy.SOURCE) public @interface DataState{} @@ -6169,6 +6170,12 @@ public class TelephonyManager { public static final int DATA_DISCONNECTING = 4; /** + * Data connection state: Handover in progress. The connection is being transited from cellular + * network to IWLAN, or from IWLAN to cellular network. + */ + public static final int DATA_HANDOVER_IN_PROGRESS = 5; + + /** * Used for checking if the SDK version for {@link TelephonyManager#getDataState} is above Q. */ @ChangeId @@ -6184,6 +6191,7 @@ public class TelephonyManager { * @see #DATA_CONNECTED * @see #DATA_SUSPENDED * @see #DATA_DISCONNECTING + * @see #DATA_HANDOVER_IN_PROGRESS */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA) public int getDataState() { diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 9ed230aebfbb..4ff59b568657 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -521,11 +521,11 @@ public class ApnSetting implements Parcelable { private final boolean mAlwaysOn; /** - * Returns the MTU size of the IPv4 mobile interface to which the APN connected. Note this value - * is used only if MTU size is not provided in {@link DataCallResponse}. + * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought + * up by this APN setting. Note this value will only be used when MTU size is not provided + * in {@link DataCallResponse#getMtuV4()} during network bring up. * - * @return the MTU size of the APN - * @hide + * @return the MTU size in bytes of the route. */ public int getMtuV4() { return mMtuV4; @@ -533,10 +533,10 @@ public class ApnSetting implements Parcelable { /** * Returns the MTU size of the IPv6 mobile interface to which the APN connected. Note this value - * is used only if MTU size is not provided in {@link DataCallResponse}. + * will only be used when MTU size is not provided in {@link DataCallResponse#getMtuV6()} + * during network bring up. * - * @return the MTU size of the APN - * @hide + * @return the MTU size in bytes of the route. */ public int getMtuV6() { return mMtuV6; @@ -1753,25 +1753,25 @@ public class ApnSetting implements Parcelable { } /** - * Set the MTU size of the IPv4 mobile interface to which the APN connected. Note this value - * is used only if MTU size is not provided in {@link DataCallResponse}. + * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought + * up by this APN setting. Note this value will only be used when MTU size is not provided + * in {@link DataCallResponse#getMtuV4()} during network bring up. * - * @param mtuV4 the MTU size to set for the APN - * @hide + * @param mtuV4 the MTU size in bytes of the route. */ - public Builder setMtuV4(int mtuV4) { + public @NonNull Builder setMtuV4(int mtuV4) { this.mMtuV4 = mtuV4; return this; } /** - * Set the MTU size of the IPv6 mobile interface to which the APN connected. Note this value - * is used only if MTU size is not provided in {@link DataCallResponse}. + * Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv6 routes brought + * up by this APN setting. Note this value will only be used when MTU size is not provided + * in {@link DataCallResponse#getMtuV6()} during network bring up. * - * @param mtuV6 the MTU size to set for the APN - * @hide + * @param mtuV6 the MTU size in bytes of the route. */ - public Builder setMtuV6(int mtuV6) { + public @NonNull Builder setMtuV6(int mtuV6) { this.mMtuV6 = mtuV6; return this; } @@ -1779,15 +1779,25 @@ public class ApnSetting implements Parcelable { /** * Sets the profile id to which the APN saved in modem. * - * @param profileId the profile id to set for the APN - * @hide + * @param profileId the profile id to set for the APN. */ - public Builder setProfileId(int profileId) { + public @NonNull Builder setProfileId(int profileId) { this.mProfileId = profileId; return this; } /** + * Set if the APN setting should be persistent/non-persistent in modem. + * + * @param isPersistent {@code true} if this APN setting should be persistent/non-persistent + * in modem. + * @return The same instance of the builder. + */ + public @NonNull Builder setPersistent(boolean isPersistent) { + return setModemCognitive(isPersistent); + } + + /** * Sets if the APN setting is to be set in modem. * * @param modemCognitive if the APN setting is to be set in modem diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index 479f057eda9c..a166a5d6404c 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -38,8 +38,10 @@ import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** - * Description of a mobile data profile used for establishing - * data connections. + * Description of a mobile data profile used for establishing data networks. The data profile + * consist an {@link ApnSetting} which is needed for 2G/3G/4G networks bring up, and a + * {@link TrafficDescriptor} contains additional information that can be used for 5G standalone + * network bring up. * * @hide */ @@ -113,7 +115,9 @@ public final class DataProfile implements Parcelable { /** * @return Id of the data profile. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getProfileId()} instead. */ + @Deprecated public int getProfileId() { if (mApnSetting != null) { return mApnSetting.getProfileId(); @@ -124,9 +128,10 @@ public final class DataProfile implements Parcelable { /** * @return The APN (Access Point Name) to establish data connection. This is a string * specifically defined by the carrier. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getApnName()} instead. */ - @NonNull - public String getApn() { + @Deprecated + public @NonNull String getApn() { if (mApnSetting != null) { return TextUtils.emptyIfNull(mApnSetting.getApnName()); } @@ -135,7 +140,9 @@ public final class DataProfile implements Parcelable { /** * @return The connection protocol defined in 3GPP TS 27.007 section 10.1.1. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getProtocol()} instead. */ + @Deprecated public @ProtocolType int getProtocolType() { if (mApnSetting != null) { return mApnSetting.getProtocol(); @@ -145,7 +152,9 @@ public final class DataProfile implements Parcelable { /** * @return The authentication protocol used for this PDP context. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getAuthType()} instead. */ + @Deprecated public @AuthType int getAuthType() { if (mApnSetting != null) { return mApnSetting.getAuthType(); @@ -154,10 +163,11 @@ public final class DataProfile implements Parcelable { } /** - * @return The username for APN. Can be null. + * @return The username for APN. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getUser()} instead. */ - @Nullable - public String getUserName() { + @Deprecated + public @Nullable String getUserName() { if (mApnSetting != null) { return mApnSetting.getUser(); } @@ -165,10 +175,11 @@ public final class DataProfile implements Parcelable { } /** - * @return The password for APN. Can be null. + * @return The password for APN. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getPassword()} instead. */ - @Nullable - public String getPassword() { + @Deprecated + public @Nullable String getPassword() { if (mApnSetting != null) { return mApnSetting.getPassword(); } @@ -232,7 +243,9 @@ public final class DataProfile implements Parcelable { /** * @return The supported APN types bitmask. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getApnTypeBitmask()} instead. */ + @Deprecated public @ApnType int getSupportedApnTypesBitmask() { if (mApnSetting != null) { return mApnSetting.getApnTypeBitmask(); @@ -242,7 +255,9 @@ public final class DataProfile implements Parcelable { /** * @return The connection protocol on roaming network defined in 3GPP TS 27.007 section 10.1.1. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#getRoamingProtocol()} instead. */ + @Deprecated public @ProtocolType int getRoamingProtocolType() { if (mApnSetting != null) { return mApnSetting.getRoamingProtocol(); @@ -252,7 +267,10 @@ public final class DataProfile implements Parcelable { /** * @return The bearer bitmask indicating the applicable networks for this data profile. + * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getNetworkTypeBitmask()} + * instead. */ + @Deprecated public @NetworkTypeBitMask int getBearerBitmask() { if (mApnSetting != null) { return mApnSetting.getNetworkTypeBitmask(); @@ -262,7 +280,8 @@ public final class DataProfile implements Parcelable { /** * @return The maximum transmission unit (MTU) size in bytes. - * @deprecated use {@link #getMtuV4} or {@link #getMtuV6} instead. + * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getMtuV4()}/ + * {@link ApnSetting#getMtuV6()} instead. */ @Deprecated public int getMtu() { @@ -272,7 +291,9 @@ public final class DataProfile implements Parcelable { /** * This replaces the deprecated method getMtu. * @return The maximum transmission unit (MTU) size in bytes, for IPv4. + * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getMtuV4()} instead. */ + @Deprecated public int getMtuV4() { if (mApnSetting != null) { return mApnSetting.getMtuV4(); @@ -282,7 +303,9 @@ public final class DataProfile implements Parcelable { /** * @return The maximum transmission unit (MTU) size in bytes, for IPv6. + * @deprecated use {@link #getApnSetting()} and {@link ApnSetting#getMtuV6()} instead. */ + @Deprecated public int getMtuV6() { if (mApnSetting != null) { return mApnSetting.getMtuV6(); @@ -292,7 +315,9 @@ public final class DataProfile implements Parcelable { /** * @return {@code true} if modem must persist this data profile. + * @deprecated Use {@link #getApnSetting()} and {@link ApnSetting#isPersistent()} instead. */ + @Deprecated public boolean isPersistent() { if (mApnSetting != null) { return mApnSetting.isPersistent(); @@ -320,16 +345,16 @@ public final class DataProfile implements Parcelable { } /** - * @return The APN setting - * @hide TODO: Remove before T is released. + * @return The APN setting {@link ApnSetting}, which is used to establish data network on + * 2G/3G/4G. */ public @Nullable ApnSetting getApnSetting() { return mApnSetting; } /** - * @return The traffic descriptor - * @hide TODO: Remove before T is released. + * @return The traffic descriptor {@link TrafficDescriptor}, which can be used to establish + * data network on 5G. */ public @Nullable TrafficDescriptor getTrafficDescriptor() { return mTrafficDescriptor; @@ -539,7 +564,10 @@ public final class DataProfile implements Parcelable { * * @param profileId Network domain. * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setProfileId(int)} instead. */ + @Deprecated public @NonNull Builder setProfileId(int profileId) { mProfileId = profileId; return this; @@ -551,7 +579,10 @@ public final class DataProfile implements Parcelable { * * @param apn Access point name * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setApnName(String)} instead. */ + @Deprecated public @NonNull Builder setApn(@NonNull String apn) { mApn = apn; return this; @@ -562,7 +593,10 @@ public final class DataProfile implements Parcelable { * * @param protocolType The connection protocol defined in 3GPP TS 27.007 section 10.1.1. * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setProtocol(int)} instead. */ + @Deprecated public @NonNull Builder setProtocolType(@ProtocolType int protocolType) { mProtocolType = protocolType; return this; @@ -573,7 +607,10 @@ public final class DataProfile implements Parcelable { * * @param authType The authentication type * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setAuthType(int)} instead. */ + @Deprecated public @NonNull Builder setAuthType(@AuthType int authType) { mAuthType = authType; return this; @@ -584,7 +621,10 @@ public final class DataProfile implements Parcelable { * * @param userName The user name * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setUser(String)} instead. */ + @Deprecated public @NonNull Builder setUserName(@NonNull String userName) { mUserName = userName; return this; @@ -595,7 +635,10 @@ public final class DataProfile implements Parcelable { * * @param password The password * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setPassword(String)} (int)} instead. */ + @Deprecated public @NonNull Builder setPassword(@NonNull String password) { mPassword = password; return this; @@ -628,7 +671,10 @@ public final class DataProfile implements Parcelable { * * @param supportedApnTypesBitmask The supported APN types bitmask. * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setApnTypeBitmask(int)} instead. */ + @Deprecated public @NonNull Builder setSupportedApnTypesBitmask(@ApnType int supportedApnTypesBitmask) { mSupportedApnTypesBitmask = supportedApnTypesBitmask; return this; @@ -639,7 +685,10 @@ public final class DataProfile implements Parcelable { * * @param protocolType The connection protocol defined in 3GPP TS 27.007 section 10.1.1. * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setRoamingProtocol(int)} instead. */ + @Deprecated public @NonNull Builder setRoamingProtocolType(@ProtocolType int protocolType) { mRoamingProtocolType = protocolType; return this; @@ -651,7 +700,10 @@ public final class DataProfile implements Parcelable { * @param bearerBitmask The bearer bitmask indicating the applicable networks for this data * profile. * @return The same instance of the builder. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setNetworkTypeBitmask(int)} instead. */ + @Deprecated public @NonNull Builder setBearerBitmask(@NetworkTypeBitMask int bearerBitmask) { mBearerBitmask = bearerBitmask; return this; @@ -662,7 +714,9 @@ public final class DataProfile implements Parcelable { * * @param mtu The maximum transmission unit (MTU) size in bytes. * @return The same instance of the builder. - * @deprecated use {@link #setApnSetting(ApnSetting)} instead. + * @deprecated use {@link #setApnSetting(ApnSetting)} and + * {@link ApnSetting.Builder#setMtuV4(int)}/{@link ApnSetting.Builder#setMtuV6(int)} + * instead. */ @Deprecated public @NonNull Builder setMtu(int mtu) { @@ -672,11 +726,13 @@ public final class DataProfile implements Parcelable { /** * Set the maximum transmission unit (MTU) size in bytes, for IPv4. - * This replaces the deprecated method setMtu. * * @param mtu The maximum transmission unit (MTU) size in bytes. * @return The same instance of the builder. + * @deprecated Use {{@link #setApnSetting(ApnSetting)}} and + * {@link ApnSetting.Builder#setMtuV4(int)} instead. */ + @Deprecated public @NonNull Builder setMtuV4(int mtu) { mMtuV4 = mtu; return this; @@ -687,7 +743,10 @@ public final class DataProfile implements Parcelable { * * @param mtu The maximum transmission unit (MTU) size in bytes. * @return The same instance of the builder. + * @deprecated Use {{@link #setApnSetting(ApnSetting)}} and + * {@link ApnSetting.Builder#setMtuV6(int)} instead. */ + @Deprecated public @NonNull Builder setMtuV6(int mtu) { mMtuV6 = mtu; return this; @@ -712,19 +771,23 @@ public final class DataProfile implements Parcelable { * @param isPersistent {@code true} if this data profile was used to bring up the last * default (i.e internet) data connection successfully. * @return The same instance of the builder. + * @deprecated Use {{@link #setApnSetting(ApnSetting)}} and + * {@link ApnSetting.Builder#setPersistent(boolean)} instead. */ + @Deprecated public @NonNull Builder setPersistent(boolean isPersistent) { mPersistent = isPersistent; return this; } /** - * Set APN setting. + * Set the APN setting. Note that if an APN setting is not set here, then either + * {@link #setApn(String)} or {@link #setTrafficDescriptor(TrafficDescriptor)} must be + * called. Otherwise {@link IllegalArgumentException} will be thrown when {@link #build()} + * the data profile. * - * @param apnSetting APN setting - * @return The same instance of the builder - * - * @hide // TODO: Remove before T is released. + * @param apnSetting The APN setting. + * @return The same instance of the builder. */ public @NonNull Builder setApnSetting(@NonNull ApnSetting apnSetting) { mApnSetting = apnSetting; @@ -732,12 +795,13 @@ public final class DataProfile implements Parcelable { } /** - * Set traffic descriptor. - * - * @param trafficDescriptor Traffic descriptor - * @return The same instance of the builder + * Set the traffic descriptor. Note that if a traffic descriptor is not set here, then + * either {@link #setApnSetting(ApnSetting)} or {@link #setApn(String)} must be called. + * Otherwise {@link IllegalArgumentException} will be thrown when {@link #build()} the data + * profile. * - * @hide // TODO: Remove before T is released. + * @param trafficDescriptor The traffic descriptor. + * @return The same instance of the builder. */ public @NonNull Builder setTrafficDescriptor(@NonNull TrafficDescriptor trafficDescriptor) { mTrafficDescriptor = trafficDescriptor; @@ -745,9 +809,9 @@ public final class DataProfile implements Parcelable { } /** - * Build the DataProfile object + * Build the DataProfile object. * - * @return The data profile object + * @return The data profile object. */ public @NonNull DataProfile build() { if (mApnSetting == null && mApn != null) { diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index e181c62c67e5..68e213b2c00f 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -14,6 +14,7 @@ EMOJI_VS = 0xFE0F LANG_TO_SCRIPT = { 'as': 'Beng', + 'am': 'Latn', 'be': 'Cyrl', 'bg': 'Cyrl', 'bn': 'Beng', @@ -27,15 +28,18 @@ LANG_TO_SCRIPT = { 'eu': 'Latn', 'fr': 'Latn', 'ga': 'Latn', + 'gl': 'Latn', 'gu': 'Gujr', 'hi': 'Deva', 'hr': 'Latn', 'hu': 'Latn', 'hy': 'Armn', + 'it': 'Latn', 'ja': 'Jpan', 'kn': 'Knda', 'ko': 'Kore', 'la': 'Latn', + 'lt': 'Latn', 'ml': 'Mlym', 'mn': 'Cyrl', 'mr': 'Deva', @@ -48,6 +52,7 @@ LANG_TO_SCRIPT = { 'ta': 'Taml', 'te': 'Telu', 'tk': 'Latn', + 'uk': 'Latn', } def lang_to_script(lang_code): |