diff options
449 files changed, 15762 insertions, 6979 deletions
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index fb650319e061..0f3ea0ca298f 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -40,7 +40,7 @@ gensrcs { ":libtombstone_proto-src", "core/proto/**/*.proto", "libs/incident/**/*.proto", - ":service-permission-protos", + ":service-permission-streaming-proto-sources", ], output_extension: "srcjar", } @@ -70,7 +70,7 @@ gensrcs { ":libstats_atom_message_protos", "core/proto/**/*.proto", "libs/incident/**/*.proto", - ":service-permission-protos", + ":service-permission-streaming-proto-sources", ], output_extension: "proto.h", @@ -90,7 +90,7 @@ java_library_host { "cmds/statsd/src/**/*.proto", "core/proto/**/*.proto", "libs/incident/proto/**/*.proto", - ":service-permission-protos", + ":service-permission-streaming-proto-sources", ], proto: { include_dirs: [ @@ -126,7 +126,7 @@ java_library { ":libstats_atom_message_protos", "core/proto/**/*.proto", "libs/incident/proto/android/os/**/*.proto", - ":service-permission-protos", + ":service-permission-streaming-proto-sources", ], // Protos have lots of MissingOverride and similar. errorprone: { @@ -149,7 +149,7 @@ java_library { ":libstats_atom_message_protos", "core/proto/**/*.proto", "libs/incident/proto/android/os/**/*.proto", - ":service-permission-protos", + ":service-permission-streaming-proto-sources", ], exclude_srcs: [ "core/proto/android/privacy.proto", @@ -186,7 +186,7 @@ cc_defaults { ":libstats_atom_enum_protos", ":libstats_atom_message_protos", "core/proto/**/*.proto", - ":service-permission-protos", + ":service-permission-streaming-proto-sources", ], } diff --git a/core/api/current.txt b/core/api/current.txt index 542221c3fcc9..29cf1c77af1f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -113,8 +113,9 @@ package android { field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA"; field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS"; field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS"; - field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN"; + field @Deprecated public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN"; field public static final String MANAGE_WIFI_INTERFACES = "android.permission.MANAGE_WIFI_INTERFACES"; + field public static final String MANAGE_WIFI_NETWORK_SELECTION = "android.permission.MANAGE_WIFI_NETWORK_SELECTION"; field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS"; @@ -3335,7 +3336,7 @@ package android.accessibilityservice { } public class InputMethod { - ctor protected InputMethod(@NonNull android.accessibilityservice.AccessibilityService); + ctor public InputMethod(@NonNull android.accessibilityservice.AccessibilityService); method @Nullable public final android.accessibilityservice.InputMethod.AccessibilityInputConnection getCurrentInputConnection(); method @Nullable public final android.view.inputmethod.EditorInfo getCurrentInputEditorInfo(); method public final boolean getCurrentInputStarted(); @@ -7437,7 +7438,7 @@ package android.app.admin { method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName); method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName); method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName); - method @NonNull public android.app.admin.PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig(); + method @NonNull public java.util.List<android.app.admin.PreferentialNetworkServiceConfig> getPreferentialNetworkServiceConfigs(); method public int getRequiredPasswordComplexity(); method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName); method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName); @@ -7582,7 +7583,7 @@ package android.app.admin { method public boolean setPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName, @Nullable java.util.List<java.lang.String>); method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>); method public void setPersonalAppsSuspended(@NonNull android.content.ComponentName, boolean); - method public void setPreferentialNetworkServiceConfig(@NonNull android.app.admin.PreferentialNetworkServiceConfig); + method public void setPreferentialNetworkServiceConfigs(@NonNull java.util.List<android.app.admin.PreferentialNetworkServiceConfig>); method public void setPreferentialNetworkServiceEnabled(boolean); method public void setProfileEnabled(@NonNull android.content.ComponentName); method public void setProfileName(@NonNull android.content.ComponentName, String); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 659783c864cc..7aef9a6b767a 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -50,22 +50,6 @@ package android.app { method public void onCanceled(@NonNull android.app.PendingIntent); } - public class PropertyInvalidatedCache<Query, Result> { - ctor public PropertyInvalidatedCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>); - method public final void disableForCurrentProcess(); - method public final void invalidateCache(); - method public static void invalidateCache(@NonNull String, @NonNull String); - method @Nullable public final Result query(@NonNull Query); - field public static final String MODULE_BLUETOOTH = "bluetooth"; - field public static final String MODULE_TELEPHONY = "telephony"; - } - - public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> { - ctor public PropertyInvalidatedCache.QueryHandler(); - method @Nullable public abstract R apply(@NonNull Q); - method public boolean shouldBypassCache(@NonNull Q); - } - public class StatusBarManager { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean); } @@ -118,6 +102,7 @@ package android.content.pm { public abstract class PackageManager { method @NonNull public String getPermissionControllerPackageName(); method @NonNull public String getSdkSandboxPackageName(); + field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH"; field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000 } @@ -354,13 +339,29 @@ package android.os { field public static final int DEVICE_INITIAL_SDK_INT; } + public class IpcDataCache<Query, Result> { + ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>); + method public void disableForCurrentProcess(); + method public static void disableForCurrentProcess(@NonNull String); + method public void invalidateCache(); + method public static void invalidateCache(@NonNull String, @NonNull String); + method @Nullable public Result query(@NonNull Query); + field public static final String MODULE_BLUETOOTH = "bluetooth"; + } + + public abstract static class IpcDataCache.QueryHandler<Q, R> { + ctor public IpcDataCache.QueryHandler(); + method @Nullable public abstract R apply(@NonNull Q); + method public boolean shouldBypassCache(@NonNull Q); + } + public interface Parcelable { method public default int getStability(); } public class Process { + method public static final int getAppUidForSdkSandboxUid(int); method public static final boolean isSdkSandboxUid(int); - method public static final int sdkSandboxToAppUid(int); method public static final int toSdkSandboxUid(int); field public static final int NFC_UID = 1027; // 0x403 field public static final int VPN_UID = 1016; // 0x3f8 @@ -454,6 +455,14 @@ package android.provider { } +package android.telecom { + + public abstract class ConnectionService extends android.app.Service { + method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); + } + +} + package android.telephony { public abstract class CellSignalStrength { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5a6e8ee22128..37f3cbd84eef 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1650,13 +1650,13 @@ package android.app.cloudsearch { public final class SearchRequest implements android.os.Parcelable { method public int describeContents(); + method @NonNull public String getCallerPackageName(); method public float getMaxLatencyMillis(); method @NonNull public String getQuery(); method @NonNull public String getRequestId(); method public int getResultNumber(); method public int getResultOffset(); method @NonNull public android.os.Bundle getSearchConstraints(); - method @NonNull public String getSource(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "android.app.cloudsearch.IS_PRESUBMIT_SUGGESTION"; field public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "android.app.cloudsearch.SEARCH_PROVIDER_FILTER"; @@ -8547,7 +8547,7 @@ package android.net { public final class EthernetNetworkUpdateRequest implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.net.IpConfiguration getIpConfiguration(); - method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method @Nullable public android.net.NetworkCapabilities getNetworkCapabilities(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkUpdateRequest> CREATOR; } @@ -8557,7 +8557,7 @@ package android.net { ctor public EthernetNetworkUpdateRequest.Builder(@NonNull android.net.EthernetNetworkUpdateRequest); method @NonNull public android.net.EthernetNetworkUpdateRequest build(); method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setIpConfiguration(@NonNull android.net.IpConfiguration); - method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setNetworkCapabilities(@NonNull android.net.NetworkCapabilities); + method @NonNull public android.net.EthernetNetworkUpdateRequest.Builder setNetworkCapabilities(@Nullable android.net.NetworkCapabilities); } public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { @@ -9030,6 +9030,7 @@ package android.net.wifi.nl80211 { method public void enableVerboseLogging(boolean); method @NonNull public int[] getChannelsMhzForBand(int); method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); + method public int getMaxNumScanSsids(@NonNull String); method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); method public void notifyCountryCodeChanged(@Nullable String); @@ -10580,7 +10581,6 @@ package android.provider { field public static final String AUTO_REVOKE_DISABLED = "auto_revoke_disabled"; field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category."; field public static final String DOZE_ALWAYS_ON = "doze_always_on"; - field public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; field public static final String HUSH_GESTURE_USED = "hush_gesture_used"; field public static final String INSTANT_APPS_ENABLED = "instant_apps_enabled"; field public static final String LAST_SETUP_SHOWN = "last_setup_shown"; @@ -11405,7 +11405,7 @@ package android.service.games { public static interface GameSession.ScreenshotCallback { method public void onFailure(int); - method public void onSuccess(@NonNull android.graphics.Bitmap); + method public void onSuccess(); field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0 } @@ -12217,7 +12217,6 @@ package android.telecom { public abstract class ConnectionService extends android.app.Service { method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference); - method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); } public abstract class InCallService extends android.app.Service { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 7877677dfe71..5aec193f33d4 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -373,16 +373,17 @@ package android.app { public class PropertyInvalidatedCache<Query, Result> { ctor public PropertyInvalidatedCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>); method @NonNull public static String createPropertyName(@NonNull String, @NonNull String); - method public final void disableForCurrentProcess(); + method public void disableForCurrentProcess(); + method public static void disableForCurrentProcess(@NonNull String); method public static void disableForTestMode(); method public final void disableInstance(); method public final void disableSystemWide(); method public final void forgetDisableLocal(); method public boolean getDisabledState(); - method public final void invalidateCache(); + method public void invalidateCache(); method public static void invalidateCache(@NonNull String, @NonNull String); method public final boolean isDisabled(); - method @Nullable public final Result query(@NonNull Query); + method @Nullable public Result query(@NonNull Query); method public static void setTestMode(boolean); method public void testPropertyName(); field public static final String MODULE_BLUETOOTH = "bluetooth"; @@ -621,7 +622,7 @@ package android.app.blob { package android.app.cloudsearch { public static final class SearchRequest.Builder { - method @NonNull public android.app.cloudsearch.SearchRequest.Builder setSource(@NonNull String); + method @NonNull public android.app.cloudsearch.SearchRequest.Builder setCallerPackageName(@NonNull String); } } @@ -1726,6 +1727,19 @@ package android.os { method @NonNull public static byte[] digest(@NonNull java.io.InputStream, @NonNull String) throws java.io.IOException, java.security.NoSuchAlgorithmException; } + public class IpcDataCache<Query, Result> extends android.app.PropertyInvalidatedCache<Query,Result> { + ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>); + method public static void disableForCurrentProcess(@NonNull String); + method public static void invalidateCache(@NonNull String, @NonNull String); + field public static final String MODULE_BLUETOOTH = "bluetooth"; + field public static final String MODULE_SYSTEM = "system_server"; + field public static final String MODULE_TEST = "test"; + } + + public abstract static class IpcDataCache.QueryHandler<Q, R> extends android.app.PropertyInvalidatedCache.QueryHandler<Q,R> { + ctor public IpcDataCache.QueryHandler(); + } + public final class MessageQueue { method public int postSyncBarrier(); method public void removeSyncBarrier(int); @@ -1777,9 +1791,9 @@ package android.os { } public class Process { + method public static final int getAppUidForSdkSandboxUid(int); method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException; method public static final boolean isSdkSandboxUid(int); - method public static final int sdkSandboxToAppUid(int); method public static final int toSdkSandboxUid(int); field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90 field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8 @@ -1787,6 +1801,7 @@ package android.os { field public static final int LAST_ISOLATED_UID = 99999; // 0x1869f field public static final int NFC_UID = 1027; // 0x403 field public static final int NUM_UIDS_PER_APP_ZYGOTE = 100; // 0x64 + field public static final int SDK_SANDBOX_VIRTUAL_UID = 1090; // 0x442 } public final class StrictMode { diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index c82f5f6d54fc..3cb04e710d3b 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -825,17 +825,7 @@ public abstract class AccessibilityService extends Service { for (int i = 0; i < mMagnificationControllers.size(); i++) { mMagnificationControllers.valueAt(i).onServiceConnectedLocked(); } - AccessibilityServiceInfo info = getServiceInfo(); - if (info != null) { - boolean requestIme = (info.flags - & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0; - if (requestIme && !mInputMethodInitialized) { - mInputMethod = onCreateInputMethod(); - mInputMethodInitialized = true; - } - } else { - Log.e(LOG_TAG, "AccessibilityServiceInfo is null in dispatchServiceConnected"); - } + updateInputMethod(getServiceInfo()); } if (mSoftKeyboardController != null) { mSoftKeyboardController.onServiceConnected(); @@ -846,6 +836,20 @@ public abstract class AccessibilityService extends Service { onServiceConnected(); } + private void updateInputMethod(AccessibilityServiceInfo info) { + if (info != null) { + boolean requestIme = (info.flags + & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0; + if (requestIme && !mInputMethodInitialized) { + mInputMethod = onCreateInputMethod(); + mInputMethodInitialized = true; + } else if (!requestIme & mInputMethodInitialized) { + mInputMethod = null; + mInputMethodInitialized = false; + } + } + } + /** * This method is a part of the {@link AccessibilityService} lifecycle and is * called after the system has successfully bound to the service. If is @@ -2521,6 +2525,7 @@ public abstract class AccessibilityService extends Service { */ public final void setServiceInfo(AccessibilityServiceInfo info) { mInfo = info; + updateInputMethod(info); sendServiceInfo(); } diff --git a/core/java/android/accessibilityservice/InputMethod.java b/core/java/android/accessibilityservice/InputMethod.java index 001d804b22d6..36cfd0e4341e 100644 --- a/core/java/android/accessibilityservice/InputMethod.java +++ b/core/java/android/accessibilityservice/InputMethod.java @@ -67,7 +67,7 @@ public class InputMethod { private InputConnection mStartedInputConnection; private EditorInfo mInputEditorInfo; - protected InputMethod(@NonNull AccessibilityService service) { + public InputMethod(@NonNull AccessibilityService service) { mService = service; } diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index 60e22f4ecd12..9bdddd037e21 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -360,6 +360,53 @@ public final class ApplicationExitInfo implements Parcelable { */ public static final int SUBREASON_FREEZER_BINDER_TRANSACTION = 20; + /** + * The process was killed because of force-stop, it could be due to that + * the user clicked the "Force stop" button of the application in the Settings; + * this would be set only when the reason is {@link #REASON_USER_REQUESTED}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_FORCE_STOP = 21; + + /** + * The process was killed because the user removed the application away from Recents; + * this would be set only when the reason is {@link #REASON_USER_REQUESTED}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_REMOVE_TASK = 22; + + /** + * The process was killed because the user stopped the application from the task manager; + * this would be set only when the reason is {@link #REASON_USER_REQUESTED}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_STOP_APP = 23; + + /** + * The process was killed because the user stopped the application from developer options, + * or via the adb shell commmand interface; this would be set only when the reason is + * {@link #REASON_USER_REQUESTED}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_KILL_BACKGROUND = 24; + + /** + * The process was killed because of package update; this would be set only when the reason is + * {@link #REASON_USER_REQUESTED}. + * + * For internal use only. + * @hide + */ + public static final int SUBREASON_PACKAGE_UPDATE = 25; + // If there is any OEM code which involves additional app kill reasons, it should // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000. @@ -520,6 +567,11 @@ public final class ApplicationExitInfo implements Parcelable { SUBREASON_ISOLATED_NOT_NEEDED, SUBREASON_FREEZER_BINDER_IOCTL, SUBREASON_FREEZER_BINDER_TRANSACTION, + SUBREASON_FORCE_STOP, + SUBREASON_REMOVE_TASK, + SUBREASON_STOP_APP, + SUBREASON_KILL_BACKGROUND, + SUBREASON_PACKAGE_UPDATE, }) @Retention(RetentionPolicy.SOURCE) public @interface SubReason {} @@ -1193,6 +1245,16 @@ public final class ApplicationExitInfo implements Parcelable { return "FREEZER BINDER IOCTL"; case SUBREASON_FREEZER_BINDER_TRANSACTION: return "FREEZER BINDER TRANSACTION"; + case SUBREASON_FORCE_STOP: + return "FORCE STOP"; + case SUBREASON_REMOVE_TASK: + return "REMOVE TASK"; + case SUBREASON_STOP_APP: + return "STOP APP"; + case SUBREASON_KILL_BACKGROUND: + return "KILL BACKGROUND"; + case SUBREASON_PACKAGE_UPDATE: + return "PACKAGE UPDATE"; default: return "UNKNOWN"; } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index a82ecce2dc04..4fbe232556ed 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -116,7 +116,7 @@ interface INotificationManager ParceledListSlice getNotificationChannelGroups(String pkg); boolean onlyHasDefaultChannel(String pkg, int uid); boolean areChannelsBypassingDnd(); - ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId); + ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid); boolean isPackagePaused(String pkg); void deleteNotificationHistoryItem(String pkg, int uid, long postedTime); boolean isPermissionFixed(String pkg, int userId); diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 2f202d95e0e3..df7bf7b94700 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -18,7 +18,6 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Handler; import android.os.Looper; @@ -137,6 +136,26 @@ import java.util.concurrent.atomic.AtomicLong; * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday * for the first time; on subsequent queries, we return the already-known Birthday object. * + * The second parameter to the IpcDataCache constructor is a string that identifies the "module" + * that owns the cache. There are some well-known modules (such as {@code MODULE_SYSTEM} but any + * string is permitted. The third parameters is the name of the API being cached; this, too, can + * any value. The fourth is the name of the cache. The cache is usually named after th API. + * Some things you must know about the three strings: + * <list> + * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}. + * Usually, the SELinux rules permit a process to write a system property (and therefore + * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that + * although the cache can be constructed with any module string, whatever string is chosen must be + * consistent with the SELinux configuration. + * <ul> The API name can be any string of alphanumeric characters. All caches with the same API + * are invalidated at the same time. If a server supports several caches and all are invalidated + * in common, then it is most efficient to assign the same API string to every cache. + * <ul> The cache name can be any string. In debug output, the name is used to distiguish between + * caches with the same API name. The cache name is also used when disabling caches in the + * current process. So, invalidation is based on the module+api but disabling (which is generally + * a once-per-process operation) is based on the cache name. + * </list> + * * User birthdays do occasionally change, so we have to modify the server to invalidate this * cache when necessary. That invalidation code looks like this: * @@ -192,25 +211,23 @@ import java.util.concurrent.atomic.AtomicLong; * <pre> * public class ActivityThread { * ... - * private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache - * private static final String BDAY_CACHE_KEY = "cache_key.birthdayd"; - * private final PropertyInvalidatedCache<Integer, Birthday%> mBirthdayCache = new - * PropertyInvalidatedCache<Integer, Birthday%>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) { - * {@literal @}Override - * protected Birthday recompute(Integer userId) { - * return GetService("birthdayd").getUserBirthday(userId); - * } - * {@literal @}Override - * protected boolean bypass(Integer userId) { - * return userId == NEXT_BIRTHDAY; - * } - * }; + * private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery = + * new IpcDataCache.QueryHandler<Integer, Birthday>() { + * {@literal @}Override + * public Birthday apply(Integer) { + * return GetService("birthdayd").getUserBirthday(userId); + * } + * {@literal @}Override + * public boolean shouldBypassQuery(Integer userId) { + * return userId == NEXT_BIRTHDAY; + * } + * }; * ... * } * </pre> * - * If the {@code bypass()} method returns true then the cache is not used for that - * particular query. The {@code bypass()} method is not abstract and the default + * If the {@code shouldBypassQuery()} method returns true then the cache is not used for that + * particular query. The {@code shouldBypassQuery()} method is not abstract and the default * implementation returns false. * * For security, there is a allowlist of processes that are allowed to invalidate a cache. @@ -231,14 +248,12 @@ import java.util.concurrent.atomic.AtomicLong; * @param <Result> The class holding cache entries; use a boxed primitive if possible * @hide */ -@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public class PropertyInvalidatedCache<Query, Result> { /** * This is a configuration class that customizes a cache instance. * @hide */ - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static abstract class QueryHandler<Q,R> { /** @@ -285,7 +300,6 @@ public class PropertyInvalidatedCache<Query, Result> { * The module used for bluetooth caches. * @hide */ - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static final String MODULE_BLUETOOTH = "bluetooth"; @@ -533,7 +547,6 @@ public class PropertyInvalidatedCache<Query, Result> { * @param computer The code to compute values that are not in the cache. * @hide */ - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api, @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) { @@ -792,7 +805,7 @@ public class PropertyInvalidatedCache<Query, Result> { * TODO(216112648) Remove this in favor of disableForCurrentProcess(). * @hide */ - public final void disableLocal() { + public void disableLocal() { disableForCurrentProcess(); } @@ -802,12 +815,17 @@ public class PropertyInvalidatedCache<Query, Result> { * property. * @hide */ - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi - public final void disableForCurrentProcess() { + public void disableForCurrentProcess() { disableLocal(mCacheName); } + /** @hide */ + @TestApi + public static void disableForCurrentProcess(@NonNull String cacheName) { + disableLocal(cacheName); + } + /** * Return whether a cache instance is disabled. * @hide @@ -821,9 +839,8 @@ public class PropertyInvalidatedCache<Query, Result> { * Get a value from the cache or recompute it. * @hide */ - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi - public final @Nullable Result query(@NonNull Query query) { + public @Nullable Result query(@NonNull Query query) { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED; if (bypass(query)) { @@ -964,9 +981,8 @@ public class PropertyInvalidatedCache<Query, Result> { * PropertyInvalidatedCache is keyed on a particular property value. * @hide */ - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi - public final void invalidateCache() { + public void invalidateCache() { invalidateCache(mPropertyName); } @@ -974,7 +990,6 @@ public class PropertyInvalidatedCache<Query, Result> { * Invalidate caches in all processes that are keyed for the module and api. * @hide */ - @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static void invalidateCache(@NonNull String module, @NonNull String api) { invalidateCache(createPropertyName(module, api)); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8d4e0d6c1f42..4c7b91056ca1 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,8 @@ package android.app.admin; +import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest.permission; @@ -11031,7 +11033,7 @@ public class DevicePolicyManager { } /** - * Sets whether preferential network service is enabled on the work profile. + * Sets whether preferential network service is enabled. * For example, an organization can have a deal/agreement with a carrier that all of * the work data from its employees’ devices will be sent via a network service dedicated * for enterprise use. @@ -11039,75 +11041,72 @@ public class DevicePolicyManager { * An example of a supported preferential network service is the Enterprise * slice on 5G networks. * - * By default, preferential network service is disabled on the work profile on supported - * carriers and devices. Admins can explicitly enable it with this API. - * On fully-managed devices this method is unsupported because all traffic is considered - * work traffic. + * By default, preferential network service is disabled on the work profile and + * fully managed devices, on supported carriers and devices. + * Admins can explicitly enable it with this API. * * <p> This method enables preferential network service with a default configuration. - * To fine-tune the configuration, use {@link #setPreferentialNetworkServiceConfig) instead. + * To fine-tune the configuration, use {@link #setPreferentialNetworkServiceConfigs) instead. + * <p> Before Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * this method can be called by the profile owner of a managed profile. + * <p> Starting from Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * This method can be called by the profile owner of a managed profile + * or device owner. * - * <p>This method can only be called by the profile owner of a managed profile. * @param enabled whether preferential network service should be enabled. - * @throws SecurityException if the caller is not the profile owner. + * @throws SecurityException if the caller is not the profile owner or device owner. **/ public void setPreferentialNetworkServiceEnabled(boolean enabled) { throwIfParentInstance("setPreferentialNetworkServiceEnabled"); - if (mService == null) { - return; - } - - try { - mService.setPreferentialNetworkServiceEnabled(enabled); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + PreferentialNetworkServiceConfig.Builder configBuilder = + new PreferentialNetworkServiceConfig.Builder(); + configBuilder.setEnabled(enabled); + if (enabled) { + configBuilder.setNetworkId(NET_ENTERPRISE_ID_1); } + setPreferentialNetworkServiceConfigs(List.of(configBuilder.build())); } /** * Indicates whether preferential network service is enabled. * - * <p>This method can be called by the profile owner of a managed profile. + * <p> Before Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * This method can be called by the profile owner of a managed profile. + * <p> Starting from Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * This method can be called by the profile owner of a managed profile + * or device owner. * * @return whether preferential network service is enabled. - * @throws SecurityException if the caller is not the profile owner. + * @throws SecurityException if the caller is not the profile owner or device owner. */ public boolean isPreferentialNetworkServiceEnabled() { throwIfParentInstance("isPreferentialNetworkServiceEnabled"); - if (mService == null) { - return false; - } - try { - return mService.isPreferentialNetworkServiceEnabled(myUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getPreferentialNetworkServiceConfigs().stream().anyMatch(c -> c.isEnabled()); } /** - * Sets preferential network configuration on the work profile. + * Sets preferential network configurations. * {@see PreferentialNetworkServiceConfig} * * An example of a supported preferential network service is the Enterprise * slice on 5G networks. * - * By default, preferential network service is disabled on the work profile on supported - * carriers and devices. Admins can explicitly enable it with this API. - * On fully-managed devices this method is unsupported because all traffic is considered - * work traffic. + * By default, preferential network service is disabled on the work profile and fully managed + * devices, on supported carriers and devices. Admins can explicitly enable it with this API. + * If admin wants to have multiple enterprise slices, + * it can be configured by passing list of {@link PreferentialNetworkServiceConfig} objects. * - * <p>This method can only be called by the profile owner of a managed profile. - * @param preferentialNetworkServiceConfig preferential network configuration. - * @throws SecurityException if the caller is not the profile owner. + * @param preferentialNetworkServiceConfigs list of preferential network configurations. + * @throws SecurityException if the caller is not the profile owner or device owner. **/ - public void setPreferentialNetworkServiceConfig( - @NonNull PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) { - throwIfParentInstance("setPreferentialNetworkServiceConfig"); + public void setPreferentialNetworkServiceConfigs( + @NonNull List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) { + throwIfParentInstance("setPreferentialNetworkServiceConfigs"); if (mService == null) { return; } try { - mService.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfig); + mService.setPreferentialNetworkServiceConfigs(preferentialNetworkServiceConfigs); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -11117,18 +11116,16 @@ public class DevicePolicyManager { * Get preferential network configuration * {@see PreferentialNetworkServiceConfig} * - * <p>This method can be called by the profile owner of a managed profile. - * * @return preferential network configuration. - * @throws SecurityException if the caller is not the profile owner. + * @throws SecurityException if the caller is not the profile owner or device owner. */ - public @NonNull PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() { - throwIfParentInstance("getPreferentialNetworkServiceConfig"); + public @NonNull List<PreferentialNetworkServiceConfig> getPreferentialNetworkServiceConfigs() { + throwIfParentInstance("getPreferentialNetworkServiceConfigs"); if (mService == null) { - return PreferentialNetworkServiceConfig.DEFAULT; + return List.of(PreferentialNetworkServiceConfig.DEFAULT); } try { - return mService.getPreferentialNetworkServiceConfig(); + return mService.getPreferentialNetworkServiceConfigs(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -13612,13 +13609,18 @@ public class DevicePolicyManager { } /** - * Called by device owner to add an override APN. + * Called by device owner or profile owner to add an override APN. * * <p>This method may returns {@code -1} if {@code apnSetting} conflicts with an existing * override APN. Update the existing conflicted APN with * {@link #updateOverrideApn(ComponentName, int, ApnSetting)} instead of adding a new entry. * <p>Two override APNs are considered to conflict when all the following APIs return * the same values on both override APNs: + * <p> Before Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * Only device owners can add APNs. + * <p> Starting from Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * Device and profile owners can add enterprise APNs + * ({@link ApnSetting#TYPE_ENTERPRISE}), while only device owners can add other type of APNs. * <ul> * <li>{@link ApnSetting#getOperatorNumeric()}</li> * <li>{@link ApnSetting#getApnName()}</li> @@ -13637,7 +13639,8 @@ public class DevicePolicyManager { * @param apnSetting the override APN to insert * @return The {@code id} of inserted override APN. Or {@code -1} when failed to insert into * the database. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException If request is for enterprise APN {@code admin} is either device + * owner or profile owner and in all other types of APN if {@code admin} is not a device owner. * * @see #setOverrideApnsEnabled(ComponentName, boolean) */ @@ -13654,20 +13657,26 @@ public class DevicePolicyManager { } /** - * Called by device owner to update an override APN. + * Called by device owner or profile owner to update an override APN. * * <p>This method may returns {@code false} if there is no override APN with the given * {@code apnId}. * <p>This method may also returns {@code false} if {@code apnSetting} conflicts with an * existing override APN. Update the existing conflicted APN instead. * <p>See {@link #addOverrideApn} for the definition of conflict. + * <p> Before Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * Only device owners can update APNs. + * <p> Starting from Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * Device and profile owners can update enterprise APNs + * ({@link ApnSetting#TYPE_ENTERPRISE}), while only device owners can update other type of APNs. * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param apnId the {@code id} of the override APN to update * @param apnSetting the override APN to update * @return {@code true} if the required override APN is successfully updated, * {@code false} otherwise. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException If request is for enterprise APN {@code admin} is either device + * owner or profile owner and in all other types of APN if {@code admin} is not a device owner. * * @see #setOverrideApnsEnabled(ComponentName, boolean) */ @@ -13685,16 +13694,22 @@ public class DevicePolicyManager { } /** - * Called by device owner to remove an override APN. + * Called by device owner or profile owner to remove an override APN. * * <p>This method may returns {@code false} if there is no override APN with the given * {@code apnId}. + * <p> Before Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * Only device owners can remove APNs. + * <p> Starting from Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}: + * Device and profile owners can remove enterprise APNs + * ({@link ApnSetting#TYPE_ENTERPRISE}), while only device owners can remove other type of APNs. * * @param admin which {@link DeviceAdminReceiver} this request is associated with * @param apnId the {@code id} of the override APN to remove * @return {@code true} if the required override APN is successfully removed, {@code false} * otherwise. - * @throws SecurityException if {@code admin} is not a device owner. + * @throws SecurityException If request is for enterprise APN {@code admin} is either device + * owner or profile owner and in all other types of APN if {@code admin} is not a device owner. * * @see #setOverrideApnsEnabled(ComponentName, boolean) */ diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 9d28ddefda7b..77db14654592 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -285,12 +285,9 @@ interface IDevicePolicyManager { void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled); boolean isSecondaryLockscreenEnabled(in UserHandle userHandle); - void setPreferentialNetworkServiceEnabled(in boolean enabled); - boolean isPreferentialNetworkServiceEnabled(int userHandle); - - void setPreferentialNetworkServiceConfig( - in PreferentialNetworkServiceConfig preferentialNetworkServiceConfig); - PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig(); + void setPreferentialNetworkServiceConfigs( + in List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs); + List<PreferentialNetworkServiceConfig> getPreferentialNetworkServiceConfigs(); void setLockTaskPackages(in ComponentName who, in String[] packages); String[] getLockTaskPackages(in ComponentName who); diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java index 2849139c606b..54170a2a187f 100644 --- a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java +++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java @@ -28,7 +28,7 @@ import java.util.Objects; /** * Network configuration to be set for the user profile - * {@see DevicePolicyManager#setPreferentialNetworkServiceConfig}. + * {@see DevicePolicyManager#setPreferentialNetworkServiceConfigs}. */ public final class PreferentialNetworkServiceConfig implements Parcelable { final boolean mIsEnabled; @@ -147,8 +147,6 @@ public final class PreferentialNetworkServiceConfig implements Parcelable { /** * @return preference enterprise identifier. - * valid values starts from - * {@link #PREFERENTIAL_NETWORK_ID_1} to {@link #PREFERENTIAL_NETWORK_ID_5}. * preference identifier is applicable only if preference network service is enabled * */ @@ -286,8 +284,6 @@ public final class PreferentialNetworkServiceConfig implements Parcelable { /** * Set the preferential network identifier. - * Valid values starts from {@link #PREFERENTIAL_NETWORK_ID_1} to - * {@link #PREFERENTIAL_NETWORK_ID_5}. * preference identifier is applicable only if preferential network service is enabled. * @param preferenceId preference Id * @return The builder to facilitate chaining. diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java index 4d6507abfd61..bf783255b3d9 100644 --- a/core/java/android/app/cloudsearch/SearchRequest.java +++ b/core/java/android/app/cloudsearch/SearchRequest.java @@ -100,7 +100,7 @@ public final class SearchRequest implements Parcelable { * */ @NonNull - private String mSource; + private String mCallerPackageName; private SearchRequest(Parcel in) { this.mQuery = in.readString(); @@ -109,17 +109,17 @@ public final class SearchRequest implements Parcelable { this.mMaxLatencyMillis = in.readFloat(); this.mSearchConstraints = in.readBundle(); this.mId = in.readString(); - this.mSource = in.readString(); + this.mCallerPackageName = in.readString(); } private SearchRequest(String query, int resultOffset, int resultNumber, float maxLatencyMillis, - Bundle searchConstraints, String source) { + Bundle searchConstraints, String callerPackageName) { mQuery = query; mResultOffset = resultOffset; mResultNumber = resultNumber; mMaxLatencyMillis = maxLatencyMillis; mSearchConstraints = searchConstraints; - mSource = source; + mCallerPackageName = callerPackageName; } /** Returns the original query. */ @@ -151,8 +151,8 @@ public final class SearchRequest implements Parcelable { /** Gets the caller's package name. */ @NonNull - public String getSource() { - return mSource; + public String getCallerPackageName() { + return mCallerPackageName; } /** Returns the search request id, which is used to identify the request. */ @@ -169,8 +169,8 @@ public final class SearchRequest implements Parcelable { * * @hide */ - public void setSource(@NonNull String source) { - this.mSource = source; + public void setCallerPackageName(@NonNull String callerPackageName) { + this.mCallerPackageName = callerPackageName; } private SearchRequest(Builder b) { @@ -179,7 +179,7 @@ public final class SearchRequest implements Parcelable { mResultNumber = b.mResultNumber; mMaxLatencyMillis = b.mMaxLatencyMillis; mSearchConstraints = requireNonNull(b.mSearchConstraints); - mSource = requireNonNull(b.mSource); + mCallerPackageName = requireNonNull(b.mCallerPackageName); } /** @@ -207,7 +207,7 @@ public final class SearchRequest implements Parcelable { dest.writeFloat(this.mMaxLatencyMillis); dest.writeBundle(this.mSearchConstraints); dest.writeString(getRequestId()); - dest.writeString(this.mSource); + dest.writeString(this.mCallerPackageName); } @Override @@ -231,7 +231,7 @@ public final class SearchRequest implements Parcelable { && mResultNumber == that.mResultNumber && mMaxLatencyMillis == that.mMaxLatencyMillis && Objects.equals(mSearchConstraints, that.mSearchConstraints) - && Objects.equals(mSource, that.mSource); + && Objects.equals(mCallerPackageName, that.mCallerPackageName); } @Override @@ -246,14 +246,15 @@ public final class SearchRequest implements Parcelable { } return String.format("SearchRequest: {query:%s,offset:%d;number:%d;max_latency:%f;" - + "is_presubmit:%b;search_provider:%s;source:%s}", mQuery, mResultOffset, - mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider, mSource); + + "is_presubmit:%b;search_provider:%s;callerPackageName:%s}", mQuery, + mResultOffset, mResultNumber, mMaxLatencyMillis, isPresubmit, searchProvider, + mCallerPackageName); } @Override public int hashCode() { return Objects.hash(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis, - mSearchConstraints, mSource); + mSearchConstraints, mCallerPackageName); } /** @@ -268,7 +269,7 @@ public final class SearchRequest implements Parcelable { private int mResultNumber; private float mMaxLatencyMillis; private Bundle mSearchConstraints; - private String mSource; + private String mCallerPackageName; /** * @@ -284,7 +285,7 @@ public final class SearchRequest implements Parcelable { mResultNumber = 10; mMaxLatencyMillis = 200; mSearchConstraints = Bundle.EMPTY; - mSource = "DEFAULT_CALLER"; + mCallerPackageName = "DEFAULT_CALLER"; } /** Sets the input query. */ @@ -329,8 +330,8 @@ public final class SearchRequest implements Parcelable { */ @NonNull @TestApi - public Builder setSource(@NonNull String source) { - this.mSource = source; + public Builder setCallerPackageName(@NonNull String callerPackageName) { + this.mCallerPackageName = callerPackageName; return this; } @@ -343,7 +344,7 @@ public final class SearchRequest implements Parcelable { } return new SearchRequest(mQuery, mResultOffset, mResultNumber, mMaxLatencyMillis, - mSearchConstraints, mSource); + mSearchConstraints, mCallerPackageName); } } } diff --git a/core/java/android/app/trust/TEST_MAPPING b/core/java/android/app/trust/TEST_MAPPING new file mode 100644 index 000000000000..b9c46bfbba8c --- /dev/null +++ b/core/java/android/app/trust/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "TrustTests", + "options": [ + { + "include-filter": "android.trust.test" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 41b1a1feae80..cbb51838507b 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -28,6 +28,8 @@ import android.os.Parcelable; import android.os.UserHandle; import android.util.ArraySet; +import com.android.internal.util.Preconditions; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -82,7 +84,7 @@ public final class VirtualDeviceParams implements Parcelable { public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; private final int mLockState; - private final ArraySet<UserHandle> mUsersWithMatchingAccounts; + @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts; @NonNull private final ArraySet<ComponentName> mAllowedActivities; @NonNull private final ArraySet<ComponentName> mBlockedActivities; @ActivityPolicy @@ -94,10 +96,14 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull Set<ComponentName> allowedActivities, @NonNull Set<ComponentName> blockedActivities, @ActivityPolicy int defaultActivityPolicy) { + Preconditions.checkNotNull(usersWithMatchingAccounts); + Preconditions.checkNotNull(allowedActivities); + Preconditions.checkNotNull(blockedActivities); + mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); - mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); - mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); + mAllowedActivities = new ArraySet<>(allowedActivities); + mBlockedActivities = new ArraySet<>(blockedActivities); mDefaultActivityPolicy = defaultActivityPolicy; } @@ -130,30 +136,24 @@ public final class VirtualDeviceParams implements Parcelable { } /** - * Returns the set of activities allowed to be streamed, or {@code null} if all activities are + * Returns the set of activities allowed to be streamed, or empty set if all activities are * allowed, except the ones explicitly blocked. * * @see Builder#setAllowedActivities(Set) */ @NonNull public Set<ComponentName> getAllowedActivities() { - if (mAllowedActivities == null) { - return Collections.emptySet(); - } return Collections.unmodifiableSet(mAllowedActivities); } /** - * Returns the set of activities that are blocked from streaming, or {@code null} to indicate + * Returns the set of activities that are blocked from streaming, or empty set to indicate * that all activities in {@link #getAllowedActivities} are allowed. * * @see Builder#setBlockedActivities(Set) */ @NonNull public Set<ComponentName> getBlockedActivities() { - if (mBlockedActivities == null) { - return Collections.emptySet(); - } return Collections.unmodifiableSet(mBlockedActivities); } @@ -237,7 +237,7 @@ public final class VirtualDeviceParams implements Parcelable { public static final class Builder { private @LockState int mLockState = LOCK_STATE_DEFAULT; - private Set<UserHandle> mUsersWithMatchingAccounts; + @NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();; @NonNull private Set<ComponentName> mBlockedActivities = Collections.emptySet(); @NonNull private Set<ComponentName> mAllowedActivities = Collections.emptySet(); @ActivityPolicy @@ -282,6 +282,7 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setUsersWithMatchingAccounts( @NonNull Set<UserHandle> usersWithMatchingAccounts) { + Preconditions.checkNotNull(usersWithMatchingAccounts); mUsersWithMatchingAccounts = usersWithMatchingAccounts; return this; } @@ -301,6 +302,7 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) { + Preconditions.checkNotNull(allowedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) { throw new IllegalArgumentException( @@ -327,6 +329,7 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) { + Preconditions.checkNotNull(blockedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) { throw new IllegalArgumentException( @@ -343,9 +346,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public VirtualDeviceParams build() { - if (mUsersWithMatchingAccounts == null) { - mUsersWithMatchingAccounts = Collections.emptySet(); - } return new VirtualDeviceParams( mLockState, mUsersWithMatchingAccounts, diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 673127e9f808..2961b5505794 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -2084,7 +2084,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { splitNames = source.createString8Array(); splitSourceDirs = source.createString8Array(); splitPublicSourceDirs = source.createString8Array(); - splitDependencies = source.readSparseArray(null); + splitDependencies = source.readSparseArray(null, int[].class); nativeLibraryDir = source.readString8(); secondaryNativeLibraryDir = source.readString8(); nativeLibraryRootDir = source.readString8(); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index e9e6cd3a54c5..0f236dfe1a8b 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -497,21 +497,10 @@ interface IPackageManager { void enterSafeMode(); @UnsupportedAppUsage boolean isSafeMode(); - void systemReady(); @UnsupportedAppUsage boolean hasSystemUidErrors(); /** - * Ask the package manager to fstrim the disk if needed. - */ - void performFstrimIfNeeded(); - - /** - * Ask the package manager to update packages if needed. - */ - void updatePackagesIfNeeded(); - - /** * Notify the package manager that a package is going to be used and why. * * See PackageManager.NOTIFY_PACKAGE_USE_* for reasons. diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java index 410e106ce584..148eacc0c4d4 100644 --- a/core/java/android/content/pm/PackageInfoLite.java +++ b/core/java/android/content/pm/PackageInfoLite.java @@ -79,6 +79,11 @@ public class PackageInfoLite implements Parcelable { public boolean debuggable; /** + * Indicates if this apk is a sdk. + */ + public boolean isSdkLibrary; + + /** * Specifies the recommended install location. Can be one of * {@link InstallLocationUtils#RECOMMEND_INSTALL_INTERNAL} to install on internal storage, * {@link InstallLocationUtils#RECOMMEND_INSTALL_EXTERNAL} to install on external media, diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 3bdef50c26f5..450e09a307bf 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2433,15 +2433,6 @@ public class PackageInstaller { /** {@hide} */ private static final int[] NO_SESSIONS = {}; - /** @hide */ - @IntDef(prefix = { "SESSION_" }, value = { - SESSION_NO_ERROR, - SESSION_VERIFICATION_FAILED, - SESSION_ACTIVATION_FAILED, - SESSION_UNKNOWN_ERROR, - SESSION_CONFLICT}) - @Retention(RetentionPolicy.SOURCE) - public @interface SessionErrorCode {} /** * @deprecated use {@link #SESSION_NO_ERROR}. */ @@ -3125,7 +3116,7 @@ public class PackageInstaller { * If something went wrong with a staged session, clients can check this error code to * understand which kind of failure happened. Only meaningful if {@code isStaged} is true. */ - public @SessionErrorCode int getStagedSessionErrorCode() { + public int getStagedSessionErrorCode() { checkSessionIsStaged(); return mSessionErrorCode; } @@ -3140,7 +3131,7 @@ public class PackageInstaller { } /** {@hide} */ - public void setSessionErrorCode(@SessionErrorCode int errorCode, String errorMessage) { + public void setSessionErrorCode(int errorCode, String errorMessage) { mSessionErrorCode = errorCode; mSessionErrorMessage = errorMessage; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2915d852cc26..f9beaa7cd0e9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2205,6 +2205,14 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_BAD_PERMISSION_GROUP = -127; + /** + * Installation failed return code: an error occurred during the activation phase of this + * session. + * + * @hide + */ + public static final int INSTALL_ACTIVATION_FAILED = -128; + /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, @@ -4254,8 +4262,9 @@ public abstract class PackageManager { * for more details. * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String EXTRA_VERIFICATION_ROOT_HASH = - "android.content.pm.extra.EXTRA_VERIFICATION_ROOT_HASH"; + "android.content.pm.extra.VERIFICATION_ROOT_HASH"; /** * Extra field name for the ID of a intent filter pending verification. diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java index 5ffb958082fb..269bec256282 100644 --- a/core/java/android/content/pm/parsing/ApkLite.java +++ b/core/java/android/content/pm/parsing/ApkLite.java @@ -133,6 +133,11 @@ public class ApkLite { */ private final boolean mHasDeviceAdminReceiver; + /** + * Indicates if this apk is a sdk. + */ + private final boolean mIsSdkLibrary; + public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit, String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode, int versionCodeMajor, int revisionCode, int installLocation, @@ -143,7 +148,7 @@ public class ApkLite { String requiredSystemPropertyName, String requiredSystemPropertyValue, int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy, Set<String> requiredSplitTypes, Set<String> splitTypes, - boolean hasDeviceAdminReceiver) { + boolean hasDeviceAdminReceiver, boolean isSdkLibrary) { mPath = path; mPackageName = packageName; mSplitName = splitName; @@ -176,6 +181,7 @@ public class ApkLite { mTargetSdkVersion = targetSdkVersion; mRollbackDataPolicy = rollbackDataPolicy; mHasDeviceAdminReceiver = hasDeviceAdminReceiver; + mIsSdkLibrary = isSdkLibrary; } /** @@ -473,11 +479,19 @@ public class ApkLite { return mHasDeviceAdminReceiver; } + /** + * Indicates if this apk is a sdk. + */ + @DataClass.Generated.Member + public boolean isIsSdkLibrary() { + return mIsSdkLibrary; + } + @DataClass.Generated( - time = 1635266936769L, + time = 1643063342990L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 165cae826187..5680bcd2e2e6 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -87,6 +87,7 @@ public class ApkLiteParseUtils { private static final String TAG_USES_SDK = "uses-sdk"; private static final String TAG_USES_SPLIT = "uses-split"; private static final String TAG_MANIFEST = "manifest"; + private static final String TAG_SDK_LIBRARY = "sdk-library"; private static final int SDK_VERSION = Build.VERSION.SDK_INT; private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES; @@ -449,6 +450,8 @@ public class ApkLiteParseUtils { boolean hasDeviceAdminReceiver = false; + boolean isSdkLibrary = false; + // Only search the tree when the tag is the direct child of <manifest> tag int type; final int searchDepth = parser.getDepth() + 1; @@ -506,6 +509,8 @@ public class ApkLiteParseUtils { } else if (TAG_RECEIVER.equals(parser.getName())) { hasDeviceAdminReceiver |= isDeviceAdminReceiver( parser, hasBindDeviceAdminPermission); + } else if (TAG_SDK_LIBRARY.equals(parser.getName())) { + isSdkLibrary = true; } } } else if (TAG_OVERLAY.equals(parser.getName())) { @@ -598,7 +603,7 @@ public class ApkLiteParseUtils { overlayIsStatic, overlayPriority, requiredSystemPropertyName, requiredSystemPropertyValue, minSdkVersion, targetSdkVersion, rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second, - hasDeviceAdminReceiver)); + hasDeviceAdminReceiver, isSdkLibrary)); } private static boolean isDeviceAdminReceiver( diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java index 5f5e81253b31..e2789c93516f 100644 --- a/core/java/android/content/pm/parsing/PackageLite.java +++ b/core/java/android/content/pm/parsing/PackageLite.java @@ -105,6 +105,10 @@ public class PackageLite { * or locally compiled variants. */ private final boolean mUseEmbeddedDex; + /** + * Indicates if this package is a sdk. + */ + private final boolean mIsSdkLibrary; public PackageLite(String path, String baseApkPath, ApkLite baseApk, String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames, @@ -131,6 +135,7 @@ public class PackageLite { mRequiredSplitTypes = requiredSplitTypes; mSplitRequired = (baseApk.isSplitRequired() || hasAnyRequiredSplitTypes()); mProfileableByShell = baseApk.isProfileableByShell(); + mIsSdkLibrary = baseApk.isIsSdkLibrary(); mSplitNames = splitNames; mSplitTypes = splitTypes; mIsFeatureSplits = isFeatureSplits; @@ -401,11 +406,20 @@ public class PackageLite { return mUseEmbeddedDex; } + /** + * Indicates if this package is a sdk. + */ + @DataClass.Generated.Member + public boolean isIsSdkLibrary() { + return mIsSdkLibrary; + } + @DataClass.Generated( - time = 1628562559343L, + time = 1643132127068L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = + "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java index f34e2bf5ddc2..698cc76f6325 100644 --- a/core/java/android/hardware/CameraSessionStats.java +++ b/core/java/android/hardware/CameraSessionStats.java @@ -59,6 +59,7 @@ public class CameraSessionStats implements Parcelable { private long mRequestCount; private long mResultErrorCount; private boolean mDeviceError; + private float mMaxPreviewFps; private ArrayList<CameraStreamStats> mStreamStats; public CameraSessionStats() { @@ -67,6 +68,7 @@ public class CameraSessionStats implements Parcelable { mApiLevel = -1; mIsNdk = false; mLatencyMs = -1; + mMaxPreviewFps = 0; mSessionType = -1; mInternalReconfigure = -1; mRequestCount = 0; @@ -77,7 +79,7 @@ public class CameraSessionStats implements Parcelable { public CameraSessionStats(String cameraId, int facing, int newCameraState, String clientName, int apiLevel, boolean isNdk, int creationDuration, - int sessionType, int internalReconfigure) { + float maxPreviewFps, int sessionType, int internalReconfigure) { mCameraId = cameraId; mFacing = facing; mNewCameraState = newCameraState; @@ -85,6 +87,7 @@ public class CameraSessionStats implements Parcelable { mApiLevel = apiLevel; mIsNdk = isNdk; mLatencyMs = creationDuration; + mMaxPreviewFps = maxPreviewFps; mSessionType = sessionType; mInternalReconfigure = internalReconfigure; mStreamStats = new ArrayList<CameraStreamStats>(); @@ -121,6 +124,7 @@ public class CameraSessionStats implements Parcelable { dest.writeInt(mApiLevel); dest.writeBoolean(mIsNdk); dest.writeInt(mLatencyMs); + dest.writeFloat(mMaxPreviewFps); dest.writeInt(mSessionType); dest.writeInt(mInternalReconfigure); dest.writeLong(mRequestCount); @@ -137,6 +141,7 @@ public class CameraSessionStats implements Parcelable { mApiLevel = in.readInt(); mIsNdk = in.readBoolean(); mLatencyMs = in.readInt(); + mMaxPreviewFps = in.readFloat(); mSessionType = in.readInt(); mInternalReconfigure = in.readInt(); mRequestCount = in.readLong(); @@ -176,6 +181,10 @@ public class CameraSessionStats implements Parcelable { return mLatencyMs; } + public float getMaxPreviewFps() { + return mMaxPreviewFps; + } + public int getSessionType() { return mSessionType; } diff --git a/core/java/android/hardware/CameraStreamStats.java b/core/java/android/hardware/CameraStreamStats.java index 823d454ee16b..7b24cc4836d0 100644 --- a/core/java/android/hardware/CameraStreamStats.java +++ b/core/java/android/hardware/CameraStreamStats.java @@ -15,8 +15,8 @@ */ package android.hardware; -import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -37,6 +37,7 @@ public class CameraStreamStats implements Parcelable { private int mWidth; private int mHeight; private int mFormat; + private float mMaxPreviewFps; private int mDataSpace; private long mUsage; private long mRequestCount; @@ -56,6 +57,7 @@ public class CameraStreamStats implements Parcelable { mWidth = 0; mHeight = 0; mFormat = 0; + mMaxPreviewFps = 0; mDataSpace = 0; mUsage = 0; mRequestCount = 0; @@ -68,13 +70,14 @@ public class CameraStreamStats implements Parcelable { mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT; } - public CameraStreamStats(int width, int height, int format, + public CameraStreamStats(int width, int height, int format, float maxPreviewFps, int dataSpace, long usage, long requestCount, long errorCount, int startLatencyMs, int maxHalBuffers, int maxAppBuffers, long dynamicRangeProfile, int streamUseCase) { mWidth = width; mHeight = height; mFormat = format; + mMaxPreviewFps = maxPreviewFps; mDataSpace = dataSpace; mUsage = usage; mRequestCount = requestCount; @@ -120,6 +123,7 @@ public class CameraStreamStats implements Parcelable { dest.writeInt(mWidth); dest.writeInt(mHeight); dest.writeInt(mFormat); + dest.writeFloat(mMaxPreviewFps); dest.writeInt(mDataSpace); dest.writeLong(mUsage); dest.writeLong(mRequestCount); @@ -138,6 +142,7 @@ public class CameraStreamStats implements Parcelable { mWidth = in.readInt(); mHeight = in.readInt(); mFormat = in.readInt(); + mMaxPreviewFps = in.readFloat(); mDataSpace = in.readInt(); mUsage = in.readLong(); mRequestCount = in.readLong(); @@ -164,6 +169,10 @@ public class CameraStreamStats implements Parcelable { return mFormat; } + public float getMaxPreviewFps() { + return mMaxPreviewFps; + } + public int getDataSpace() { return mDataSpace; } diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index abdc64c56ead..d8ebb628452a 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -298,4 +298,33 @@ public interface BiometricFingerprintConstants { * @hide */ int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; + + /** + * Whether the FingerprintAcquired message is a signal to turn off HBM + */ + static boolean shouldTurnOffHbm(@FingerprintAcquired int acquiredInfo) { + switch (acquiredInfo) { + case FINGERPRINT_ACQUIRED_START: + // Authentication just began + return false; + case FINGERPRINT_ACQUIRED_GOOD: + // Good image captured. Turn off HBM. Success/Reject comes after, which is when + // hideUdfpsOverlay will be called. + return true; + case FINGERPRINT_ACQUIRED_PARTIAL: + case FINGERPRINT_ACQUIRED_INSUFFICIENT: + case FINGERPRINT_ACQUIRED_IMAGER_DIRTY: + case FINGERPRINT_ACQUIRED_TOO_SLOW: + case FINGERPRINT_ACQUIRED_TOO_FAST: + case FINGERPRINT_ACQUIRED_IMMOBILE: + case FINGERPRINT_ACQUIRED_TOO_BRIGHT: + case FINGERPRINT_ACQUIRED_VENDOR: + // Bad image captured. Turn off HBM. Matcher will not run, so there's no need to + // keep HBM on. + return true; + case FINGERPRINT_ACQUIRED_UNKNOWN: + default: + return false; + } + } } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index d9734b493471..5981d279227d 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -781,10 +781,11 @@ public final class StreamConfigurationMap { * <li>The fpsMin and fpsMax will be a multiple 30fps.</li> * <li>The fpsMin will be no less than 30fps, the fpsMax will be no less than 120fps.</li> * <li>At least one range will be a fixed FPS range where fpsMin == fpsMax.</li> - * <li>For each fixed FPS range, there will be one corresponding variable FPS range [30, - * fps_max]. These kinds of FPS ranges are suitable for preview-only use cases where the - * application doesn't want the camera device always produce higher frame rate than the display - * refresh rate.</li> + * <li>For each fixed FPS range, there will be one corresponding variable FPS range + * [30, fps_max] or [60, fps_max]. These kinds of FPS ranges are suitable for preview-only + * use cases where the application doesn't want the camera device always produce higher frame + * rate than the display refresh rate. Both 30fps and 60fps preview rate will not be + * supported for the same recording rate.</li> * </p> * * @return an array of supported high speed video recording FPS ranges The upper bound of diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl index 648edda62171..3cca1b38e5e2 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl @@ -28,9 +28,10 @@ oneway interface IUdfpsOverlayController { // Hides the overlay. void hideUdfpsOverlay(int sensorId); - // Good image captured. Turn off HBM. Success/Reject comes after, which is when hideUdfpsOverlay - // will be called. - void onAcquiredGood(int sensorId); + // Check acquiredInfo for the acquired type (BiometricFingerprintConstants#FingerprintAcquired). + // Check BiometricFingerprintConstants#shouldTurnOffHbm for whether the acquiredInfo + // should turn off HBM. + void onAcquired(int sensorId, int acquiredInfo); // Notifies of enrollment progress changes. void onEnrollmentProgress(int sensorId, int remaining); diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index af57f793bf73..02302a20fe38 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -40,6 +40,7 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.view.IInlineSuggestionsRequestCallback; @@ -70,7 +71,7 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_SET_INPUT_CONTEXT = 20; private static final int DO_UNSET_INPUT_CONTEXT = 30; private static final int DO_START_INPUT = 32; - private static final int DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED = 35; + private static final int DO_ON_NAV_BUTTON_FLAGS_CHANGED = 35; private static final int DO_CREATE_SESSION = 40; private static final int DO_SET_SESSION_ENABLED = 45; private static final int DO_SHOW_SOFT_INPUT = 60; @@ -176,7 +177,7 @@ class IInputMethodWrapper extends IInputMethod.Stub try { inputMethod.initializeInternal((IBinder) args.arg1, (IInputMethodPrivilegedOperations) args.arg2, msg.arg1, - (boolean) args.arg3, msg.arg2 != 0); + (boolean) args.arg3, msg.arg2); } finally { args.recycle(); } @@ -196,22 +197,20 @@ class IInputMethodWrapper extends IInputMethod.Stub final EditorInfo info = (EditorInfo) args.arg3; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; final boolean restarting = args.argi5 == 1; - final boolean shouldShowImeSwitcherWhenImeIsShown = args.argi6 != 0; + @InputMethodNavButtonFlags + final int navButtonFlags = args.argi6; final InputConnection ic = inputContext != null ? new RemoteInputConnection(mTarget, inputContext, cancellationGroup) : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken, - shouldShowImeSwitcherWhenImeIsShown); + navButtonFlags); args.recycle(); return; } - case DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED: { - final boolean shouldShowImeSwitcherWhenImeIsShown = msg.arg1 != 0; - inputMethod.onShouldShowImeSwitcherWhenImeIsShownChanged( - shouldShowImeSwitcherWhenImeIsShown); + case DO_ON_NAV_BUTTON_FLAGS_CHANGED: + inputMethod.onNavButtonFlagsChanged(msg.arg1); return; - } case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs)msg.obj; inputMethod.createSession(new InputMethodSessionCallbackWrapper( @@ -301,10 +300,9 @@ class IInputMethodWrapper extends IInputMethod.Stub @Override public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, int configChanges, boolean stylusHwSupported, - boolean shouldShowImeSwitcherWhenImeIsShown) { + @InputMethodNavButtonFlags int navButtonFlags) { mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL, - configChanges, shouldShowImeSwitcherWhenImeIsShown ? 1 : 0, token, privOps, - stylusHwSupported)); + configChanges, navButtonFlags, token, privOps, stylusHwSupported)); } @BinderThread @@ -344,23 +342,21 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void startInput(IBinder startInputToken, IInputContext inputContext, - EditorInfo attribute, boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) { + EditorInfo attribute, boolean restarting, + @InputMethodNavButtonFlags int navButtonFlags) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, - inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, - shouldShowImeSwitcherWhenImeIsShown ? 1 : 0)); + inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); } @BinderThread @Override - public void onShouldShowImeSwitcherWhenImeIsShownChanged( - boolean shouldShowImeSwitcherWhenImeIsShown) { - mCaller.executeOrSendMessage(mCaller.obtainMessageI( - DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED, - shouldShowImeSwitcherWhenImeIsShown ? 1 : 0)); + public void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { + mCaller.executeOrSendMessage( + mCaller.obtainMessageI(DO_ON_NAV_BUTTON_FLAGS_CHANGED, navButtonFlags)); } @BinderThread diff --git a/core/java/android/inputmethodservice/ImsConfigurationTracker.java b/core/java/android/inputmethodservice/ImsConfigurationTracker.java index 3c788884371b..30ef0a2a6f7f 100644 --- a/core/java/android/inputmethodservice/ImsConfigurationTracker.java +++ b/core/java/android/inputmethodservice/ImsConfigurationTracker.java @@ -63,8 +63,9 @@ public final class ImsConfigurationTracker { */ @MainThread public void onBindInput(@Nullable Resources resources) { - Preconditions.checkState(mInitialized, - "onBindInput can be called only after onInitialize()."); + if (!mInitialized) { + return; + } if (mLastKnownConfig == null && resources != null) { mLastKnownConfig = new Configuration(resources.getConfiguration()); } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index fbc0732affe1..656aea18fe50 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -140,6 +140,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.ImeTracing; +import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; import com.android.internal.view.IInlineSuggestionsRequestCallback; @@ -347,7 +348,7 @@ public class InputMethodService extends AbstractInputMethodService { */ @AnyThread public static boolean canImeRenderGesturalNavButtons() { - return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false); + return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, true); } /** @@ -660,7 +661,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void initializeInternal(@NonNull IBinder token, IInputMethodPrivilegedOperations privilegedOperations, int configChanges, - boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) { + boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) { if (mDestroyed) { Log.i(TAG, "The InputMethodService has already onDestroyed()." + "Ignore the initialization."); @@ -673,8 +674,7 @@ public class InputMethodService extends AbstractInputMethodService { if (stylusHwSupported) { mInkWindow = new InkWindow(mWindow.getContext()); } - mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown( - shouldShowImeSwitcherWhenImeIsShown); + mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); attachToken(token); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -784,10 +784,9 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) { + @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) { mPrivOps.reportStartInputAsync(startInputToken); - mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown( - shouldShowImeSwitcherWhenImeIsShown); + mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); if (restarting) { restartInput(inputConnection, editorInfo); } else { @@ -801,10 +800,8 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void onShouldShowImeSwitcherWhenImeIsShownChanged( - boolean shouldShowImeSwitcherWhenImeIsShown) { - mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown( - shouldShowImeSwitcherWhenImeIsShown); + public void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { + mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); } /** diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 19fa01de4a5f..0f9075b498ae 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -16,7 +16,6 @@ package android.inputmethodservice; -import static android.content.Intent.ACTION_OVERLAY_CHANGED; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import android.animation.ValueAnimator; @@ -24,18 +23,12 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.StatusBarManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.inputmethodservice.navigationbar.NavigationBarFrame; import android.inputmethodservice.navigationbar.NavigationBarView; -import android.os.PatternMatcher; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -49,6 +42,8 @@ import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.widget.FrameLayout; +import com.android.internal.inputmethod.InputMethodNavButtonFlags; + import java.util.Objects; /** @@ -77,8 +72,7 @@ final class NavigationBarController { default void onDestroy() { } - default void setShouldShowImeSwitcherWhenImeIsShown( - boolean shouldShowImeSwitcherWhenImeIsShown) { + default void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { } default String toDebugString() { @@ -117,8 +111,8 @@ final class NavigationBarController { mImpl.onDestroy(); } - void setShouldShowImeSwitcherWhenImeIsShown(boolean shouldShowImeSwitcherWhenImeIsShown) { - mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown); + void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { + mImpl.onNavButtonFlagsChanged(navButtonFlags); } String toDebugString() { @@ -144,9 +138,6 @@ final class NavigationBarController { @Nullable Insets mLastInsets; - @Nullable - private BroadcastReceiver mSystemOverlayChangedReceiver; - private boolean mShouldShowImeSwitcherWhenImeIsShown; @Appearance @@ -359,14 +350,6 @@ final class NavigationBarController { }); } - private boolean imeDrawsImeNavBar() { - final Resources resources = mService.getResources(); - if (resources == null) { - return false; - } - return resources.getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar); - } - @Override public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) { final Window window = softInputWindow.getWindow(); @@ -379,27 +362,6 @@ final class NavigationBarController { if (mDestroyed) { return; } - mImeDrawsImeNavBar = imeDrawsImeNavBar(); - if (mSystemOverlayChangedReceiver == null) { - final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); - intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE); - intentFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); - mSystemOverlayChangedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mDestroyed) { - return; - } - mImeDrawsImeNavBar = imeDrawsImeNavBar(); - if (mImeDrawsImeNavBar) { - installNavigationBarFrameIfNecessary(); - } else { - uninstallNavigationBarFrameIfNecessary(); - } - } - }; - mService.registerReceiver(mSystemOverlayChangedReceiver, intentFilter); - } installNavigationBarFrameIfNecessary(); } @@ -412,10 +374,6 @@ final class NavigationBarController { mTintAnimator.cancel(); mTintAnimator = null; } - if (mSystemOverlayChangedReceiver != null) { - mService.unregisterReceiver(mSystemOverlayChangedReceiver); - mSystemOverlayChangedReceiver = null; - } mDestroyed = true; } @@ -448,28 +406,43 @@ final class NavigationBarController { } @Override - public void setShouldShowImeSwitcherWhenImeIsShown( - boolean shouldShowImeSwitcherWhenImeIsShown) { + public void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { if (mDestroyed) { return; } - if (mShouldShowImeSwitcherWhenImeIsShown == shouldShowImeSwitcherWhenImeIsShown) { - return; - } + + final boolean imeDrawsImeNavBar = + (navButtonFlags & InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR) != 0; + final boolean shouldShowImeSwitcherWhenImeIsShown = + (navButtonFlags & InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN) + != 0; + + mImeDrawsImeNavBar = imeDrawsImeNavBar; + final boolean prevShouldShowImeSwitcherWhenImeIsShown = + mShouldShowImeSwitcherWhenImeIsShown; mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown; - if (mNavigationBarFrame == null) { - return; - } - final NavigationBarView navigationBarView = - mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance); - if (navigationBarView == null) { - return; + if (imeDrawsImeNavBar) { + installNavigationBarFrameIfNecessary(); + if (mNavigationBarFrame == null) { + return; + } + if (mShouldShowImeSwitcherWhenImeIsShown + == prevShouldShowImeSwitcherWhenImeIsShown) { + return; + } + final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate( + NavigationBarView.class::isInstance); + if (navigationBarView == null) { + return; + } + final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT + | (shouldShowImeSwitcherWhenImeIsShown + ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0); + navigationBarView.setNavigationIconHints(hints); + } else { + uninstallNavigationBarFrameIfNecessary(); } - final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT - | (shouldShowImeSwitcherWhenImeIsShown - ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0); - navigationBarView.setNavigationIconHints(hints); } @Override diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java index 3f26fa461097..6b2db7d01a54 100644 --- a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java +++ b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java @@ -67,7 +67,7 @@ final class ButtonDispatcher { } }; - public ButtonDispatcher(int id) { + ButtonDispatcher(int id) { mId = id; } @@ -125,8 +125,8 @@ final class ButtonDispatcher { public void setImageDrawable(KeyButtonDrawable drawable) { mImageDrawable = drawable; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { if (mViews.get(i) instanceof ButtonInterface) { ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable); } @@ -143,8 +143,8 @@ final class ButtonDispatcher { } mVisibility = visibility; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setVisibility(mVisibility); } } @@ -188,8 +188,8 @@ final class ButtonDispatcher { int nextAlpha = (int) (alpha * 255); if (prevAlpha != nextAlpha) { mAlpha = nextAlpha / 255f; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setAlpha(mAlpha); } } @@ -198,8 +198,8 @@ final class ButtonDispatcher { public void setDarkIntensity(float darkIntensity) { mDarkIntensity = darkIntensity; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { if (mViews.get(i) instanceof ButtonInterface) { ((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity); } @@ -208,8 +208,8 @@ final class ButtonDispatcher { public void setDelayTouchFeedback(boolean delay) { mDelayTouchFeedback = delay; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { if (mViews.get(i) instanceof ButtonInterface) { ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay); } @@ -218,55 +218,55 @@ final class ButtonDispatcher { public void setOnClickListener(View.OnClickListener clickListener) { mClickListener = clickListener; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setOnClickListener(mClickListener); } } public void setOnTouchListener(View.OnTouchListener touchListener) { mTouchListener = touchListener; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setOnTouchListener(mTouchListener); } } public void setLongClickable(boolean isLongClickable) { mLongClickable = isLongClickable; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setLongClickable(mLongClickable); } } public void setOnLongClickListener(View.OnLongClickListener longClickListener) { mLongClickListener = longClickListener; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setOnLongClickListener(mLongClickListener); } } public void setOnHoverListener(View.OnHoverListener hoverListener) { mOnHoverListener = hoverListener; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setOnHoverListener(mOnHoverListener); } } public void setAccessibilityDelegate(AccessibilityDelegate delegate) { mAccessibilityDelegate = delegate; - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { mViews.get(i).setAccessibilityDelegate(delegate); } } public void setTranslation(int x, int y, int z) { - final int N = mViews.size(); - for (int i = 0; i < N; i++) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; i++) { final View view = mViews.get(i); view.setTranslationX(x); view.setTranslationY(y); diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java index 4adc84bf0b6f..4cfd8139d912 100644 --- a/core/java/android/inputmethodservice/navigationbar/DeadZone.java +++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java @@ -82,7 +82,7 @@ final class DeadZone { } }; - public DeadZone(NavigationBarView view) { + DeadZone(NavigationBarView view) { mNavigationBarView = view; onConfigurationChanged(Surface.ROTATION_0); } @@ -92,13 +92,15 @@ final class DeadZone { } private float getSize(long now) { - if (mSizeMax == 0) + if (mSizeMax == 0) { return 0; + } long dt = (now - mLastPokeTime); - if (dt > mHold + mDecay) + if (dt > mHold + mDecay) { return mSizeMin; - if (dt < mHold) + } else if (dt < mHold) { return mSizeMax; + } return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay); } @@ -177,8 +179,9 @@ final class DeadZone { private void poke(MotionEvent event) { mLastPokeTime = event.getEventTime(); - if (DEBUG) + if (DEBUG) { Log.v(TAG, "poked! size=" + getSize(mLastPokeTime)); + } if (mShouldFlash) mNavigationBarView.postInvalidate(); } diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java index 25a443de916b..45c8a186117d 100644 --- a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java @@ -54,30 +54,30 @@ import android.view.View; final class KeyButtonDrawable extends Drawable { public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE = - new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") { - @Override - public void setValue(KeyButtonDrawable drawable, float degree) { - drawable.setRotation(degree); - } + new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") { + @Override + public void setValue(KeyButtonDrawable drawable, float degree) { + drawable.setRotation(degree); + } - @Override - public Float get(KeyButtonDrawable drawable) { - return drawable.getRotation(); - } - }; + @Override + public Float get(KeyButtonDrawable drawable) { + return drawable.getRotation(); + } + }; public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_TRANSLATE_Y = - new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") { - @Override - public void setValue(KeyButtonDrawable drawable, float y) { - drawable.setTranslationY(y); - } + new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") { + @Override + public void setValue(KeyButtonDrawable drawable, float y) { + drawable.setTranslationY(y); + } - @Override - public Float get(KeyButtonDrawable drawable) { - return drawable.getTranslationY(); - } - }; + @Override + public Float get(KeyButtonDrawable drawable) { + return drawable.getTranslationY(); + } + }; private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); @@ -100,7 +100,7 @@ final class KeyButtonDrawable extends Drawable { } }; - public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor, + KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor, boolean horizontalFlip, Color ovalBackgroundColor) { this(d, new ShadowDrawableState(lightColor, darkColor, d instanceof AnimatedVectorDrawable, horizontalFlip, ovalBackgroundColor)); @@ -433,8 +433,8 @@ final class KeyButtonDrawable extends Drawable { final boolean mSupportsAnimation; final Color mOvalBackgroundColor; - public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor, - boolean animated, boolean horizontalFlip, Color ovalBackgroundColor) { + ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor, boolean animated, + boolean horizontalFlip, Color ovalBackgroundColor) { mLightColor = lightColor; mDarkColor = darkColor; mSupportsAnimation = animated; diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java index 38a63b661ac0..cf77c8989858 100644 --- a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java @@ -90,7 +90,7 @@ final class KeyButtonRipple extends Drawable { private Type mType = Type.ROUNDED_RECT; - public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) { + KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) { mMaxWidthResource = maxWidthResource; mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource); mTargetView = targetView; @@ -126,7 +126,7 @@ final class KeyButtonRipple extends Drawable { private void drawSoftware(Canvas canvas) { if (mGlowAlpha > 0f) { final Paint p = getRipplePaint(); - p.setAlpha((int)(mGlowAlpha * 255f)); + p.setAlpha((int) (mGlowAlpha * 255f)); final float w = getBounds().width(); final float h = getBounds().height(); @@ -412,7 +412,7 @@ final class KeyButtonRipple extends Drawable { mDrawingHardwareGlow = true; setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), - getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); + getExtendSize() / 2 - GLOW_MAX_SCALE_FACTOR * getRippleSize() / 2); startAnim.setDuration(ANIMATION_DURATION_SCALE); startAnim.setInterpolator(mInterpolator); startAnim.addListener(mAnimatorListener); @@ -420,7 +420,7 @@ final class KeyButtonRipple extends Drawable { setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2)); final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(), - getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); + getExtendSize() / 2 + GLOW_MAX_SCALE_FACTOR * getRippleSize() / 2); endAnim.setDuration(ANIMATION_DURATION_SCALE); endAnim.setInterpolator(mInterpolator); endAnim.addListener(mAnimatorListener); @@ -430,13 +430,13 @@ final class KeyButtonRipple extends Drawable { if (isHorizontal()) { mTopProp = CanvasProperty.createFloat(0f); mBottomProp = CanvasProperty.createFloat(getBounds().height()); - mRxProp = CanvasProperty.createFloat(getBounds().height()/2); - mRyProp = CanvasProperty.createFloat(getBounds().height()/2); + mRxProp = CanvasProperty.createFloat(getBounds().height() / 2); + mRyProp = CanvasProperty.createFloat(getBounds().height() / 2); } else { mLeftProp = CanvasProperty.createFloat(0f); mRightProp = CanvasProperty.createFloat(getBounds().width()); - mRxProp = CanvasProperty.createFloat(getBounds().width()/2); - mRyProp = CanvasProperty.createFloat(getBounds().width()/2); + mRxProp = CanvasProperty.createFloat(getBounds().width() / 2); + mRyProp = CanvasProperty.createFloat(getBounds().width() / 2); } mGlowScale = GLOW_MAX_SCALE_FACTOR; diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java index 74d30f8f8806..cfdb6caab9d3 100644 --- a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java @@ -200,8 +200,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface { postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); break; case MotionEvent.ACTION_MOVE: - x = (int)ev.getRawX(); - y = (int)ev.getRawY(); + x = (int) ev.getRawX(); + y = (int) ev.getRawY(); float slop = getQuickStepTouchSlopPx(getContext()); if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) { @@ -272,12 +272,13 @@ public class KeyButtonView extends ImageView implements ButtonInterface { : KeyButtonRipple.Type.ROUNDED_RECT); } + @Override public void playSoundEffect(int soundConstant) { if (!mPlaySounds) return; mAudioManager.playSoundEffect(soundConstant); } - public void sendEvent(int action, int flags) { + private void sendEvent(int action, int flags) { sendEvent(action, flags, SystemClock.uptimeMillis()); } @@ -309,8 +310,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface { switch (action) { case KeyEvent.ACTION_DOWN: handled = ims.onKeyDown(ev.getKeyCode(), ev); - mTracking = handled && ev.getRepeatCount() == 0 && - (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0; + mTracking = handled && ev.getRepeatCount() == 0 + && (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0; break; case KeyEvent.ACTION_UP: handled = ims.onKeyUp(ev.getKeyCode(), ev); diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java index f01173e0fdae..a270675239ee 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java @@ -59,4 +59,4 @@ public final class NavigationBarFrame extends FrameLayout { } return super.dispatchTouchEvent(event); } -}
\ No newline at end of file +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java index d488890b27d4..e93bda2fa939 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java @@ -121,7 +121,7 @@ public final class NavigationBarInflaterView extends FrameLayout { return CONFIG_NAV_BAR_LAYOUT_HANDLE; } - public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) { + void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) { mButtonDispatchers = buttonDispatchers; for (int i = 0; i < buttonDispatchers.size(); i++) { initiallyFill(buttonDispatchers.valueAt(i)); @@ -376,7 +376,7 @@ public final class NavigationBarInflaterView extends FrameLayout { } */ - public static String extractSize(String buttonSpec) { + private static String extractSize(String buttonSpec) { if (!buttonSpec.contains(SIZE_MOD_START)) { return null; } @@ -384,7 +384,7 @@ public final class NavigationBarInflaterView extends FrameLayout { return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END)); } - public static String extractButton(String buttonSpec) { + private static String extractButton(String buttonSpec) { if (!buttonSpec.contains(SIZE_MOD_START)) { return buttonSpec; } @@ -398,9 +398,9 @@ public final class NavigationBarInflaterView extends FrameLayout { mButtonDispatchers.valueAt(indexOfKey).addView(v); } if (v instanceof ViewGroup) { - final ViewGroup viewGroup = (ViewGroup)v; - final int N = viewGroup.getChildCount(); - for (int i = 0; i < N; i++) { + final ViewGroup viewGroup = (ViewGroup) v; + final int numChildViews = viewGroup.getChildCount(); + for (int i = 0; i < numChildViews; i++) { addToDispatchers(viewGroup.getChildAt(i)); } } diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java index a2d71054c65d..510b14e7acd4 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -48,8 +48,8 @@ import java.util.function.Consumer; * @hide */ public final class NavigationBarView extends FrameLayout { - final static boolean DEBUG = false; - final static String TAG = "NavBarView"; + private static final boolean DEBUG = false; + private static final String TAG = "NavBarView"; // Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); @@ -139,7 +139,7 @@ public final class NavigationBarView extends FrameLayout { } /** - * Applies {@param consumer} to each of the nav bar views. + * Applies {@code consumer} to each of the nav bar views. */ public void forEachView(Consumer<View> consumer) { if (mHorizontal != null) { @@ -181,7 +181,7 @@ public final class NavigationBarView extends FrameLayout { } } - public KeyButtonDrawable getBackDrawable() { + private KeyButtonDrawable getBackDrawable() { KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back); orientBackButton(drawable); return drawable; @@ -211,7 +211,7 @@ public final class NavigationBarView extends FrameLayout { // Animate the back button's rotation to the new degrees and only in portrait move up the // back button to line up with the other buttons float targetY = useAltBack - ? - dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources()) + ? -dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources()) : 0; ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable, PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees), @@ -233,6 +233,11 @@ public final class NavigationBarView extends FrameLayout { super.setLayoutDirection(layoutDirection); } + /** + * Updates the navigation icons based on {@code hints}. + * + * @param hints bit flags defined in {@link StatusBarManager}. + */ public void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; @@ -250,15 +255,7 @@ public final class NavigationBarView extends FrameLayout { updateNavButtonIcons(); } - public void setDisabledFlags(int disabledFlags) { - if (mDisabledFlags == disabledFlags) return; - - mDisabledFlags = disabledFlags; - - updateNavButtonIcons(); - } - - public void updateNavButtonIcons() { + private void updateNavButtonIcons() { // We have to replace or restore the back and home button icons when exiting or entering // carmode, respectively. Recents are not available in CarMode in nav bar so change // to recent icon is not required. @@ -319,7 +316,7 @@ public final class NavigationBarView extends FrameLayout { mHorizontal.setVisibility(View.GONE); } - public void reorient() { + private void reorient() { updateCurrentView(); final android.inputmethodservice.navigationbar.NavigationBarFrame frame = @@ -372,6 +369,11 @@ public final class NavigationBarView extends FrameLayout { } } + /** + * Updates the dark intensity. + * + * @param intensity The intensity of darkness from {@code 0.0f} to {@code 1.0f}. + */ public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) { for (int i = 0; i < mButtonDispatchers.size(); ++i) { mButtonDispatchers.valueAt(i).setDarkIntensity(intensity); diff --git a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java index 68163c35f784..9b36cc54b657 100644 --- a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java +++ b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java @@ -30,9 +30,8 @@ import java.util.ArrayList; /** * Automatically reverses the order of children as they are added. * Also reverse the width and height values of layout params - * @hide */ -public class ReverseLinearLayout extends LinearLayout { +class ReverseLinearLayout extends LinearLayout { /** If true, the layout is reversed vs. a regular linear layout */ private boolean mIsLayoutReverse; @@ -40,7 +39,7 @@ public class ReverseLinearLayout extends LinearLayout { /** If true, the layout is opposite to it's natural reversity from the layout direction */ private boolean mIsAlternativeOrder; - public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) { + ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @@ -129,7 +128,7 @@ public class ReverseLinearLayout extends LinearLayout { public static class ReverseRelativeLayout extends RelativeLayout implements Reversible { - public ReverseRelativeLayout(Context context) { + ReverseRelativeLayout(Context context) { super(context); } diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java index 0ce9c70569b5..731d1ba78299 100644 --- a/core/java/android/nfc/Tag.java +++ b/core/java/android/nfc/Tag.java @@ -142,9 +142,13 @@ public final class Tag implements Parcelable { mTagService = tagService; mConnectedTechnology = -1; + mCookie = SystemClock.elapsedRealtime(); + + if (tagService == null) { + return; + } try { - mCookie = SystemClock.elapsedRealtime(); tagService.setTagUpToDate(mCookie); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); @@ -370,6 +374,10 @@ public final class Tag implements Parcelable { /** @hide */ @UnsupportedAppUsage public INfcTag getTagService() { + if (mTagService == null) { + return null; + } + try { if (!mTagService.isTagUpToDate(mCookie)) { String id_str = ""; diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 010459d06e8d..9e47a708162d 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -293,7 +293,10 @@ public interface IBinder { * * @return Returns the result from {@link Binder#onTransact}. A successful call * generally returns true; false generally means the transaction code was not - * understood. + * understood. For a oneway call to a different process false should never be + * returned. If a oneway call is made to code in the same process (usually to + * a C++ or Rust implementation), then there are no oneway semantics, and + * false can still be returned. */ public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException; diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java new file mode 100644 index 000000000000..00734c806b7e --- /dev/null +++ b/core/java/android/os/IpcDataCache.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.app.PropertyInvalidatedCache; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FastPrintWriter; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing, + * but doesn't hold a lock across data fetches on query misses. + * + * The intended use case is caching frequently-read, seldom-changed information normally retrieved + * across interprocess communication. Imagine that you've written a user birthday information + * daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface over + * binder. That binder interface looks something like this: + * + * <pre> + * parcelable Birthday { + * int month; + * int day; + * } + * interface IUserBirthdayService { + * Birthday getUserBirthday(int userId); + * } + * </pre> + * + * Suppose the service implementation itself looks like this... + * + * <pre> + * public class UserBirthdayServiceImpl implements IUserBirthdayService { + * private final HashMap<Integer, Birthday%> mUidToBirthday; + * {@literal @}Override + * public synchronized Birthday getUserBirthday(int userId) { + * return mUidToBirthday.get(userId); + * } + * private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) { + * mUidToBirthday.clear(); + * mUidToBirthday.putAll(uidToBirthday); + * } + * } + * </pre> + * + * ... and we have a client in frameworks (loaded into every app process) that looks like this: + * + * <pre> + * public class ActivityThread { + * ... + * public Birthday getUserBirthday(int userId) { + * return GetService("birthdayd").getUserBirthday(userId); + * } + * ... + * } + * </pre> + * + * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call to + * the birthdayd process and consult its database of birthdays. If we query user birthdays + * frequently, we do a lot of work that we don't have to do, since user birthdays change + * infrequently. + * + * IpcDataCache is part of a pattern for optimizing this kind of information-querying code. Using + * {@code IpcDataCache}, you'd write the client this way: + * + * <pre> + * public class ActivityThread { + * ... + * private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery = + * new IpcDataCache.QueryHandler<Integer, Birthday>() { + * {@literal @}Override + * public Birthday apply(Integer) { + * return GetService("birthdayd").getUserBirthday(userId); + * } + * }; + * private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache + * private static final String BDAY_API = "getUserBirthday"; + * private final IpcDataCache<Integer, Birthday%> mBirthdayCache = new + * IpcDataCache<Integer, Birthday%>( + * BDAY_CACHE_MAX, MODULE_SYSTEM, BDAY_API, BDAY_API, mBirthdayQuery); + * + * public void disableUserBirthdayCache() { + * mBirthdayCache.disableForCurrentProcess(); + * } + * public void invalidateUserBirthdayCache() { + * mBirthdayCache.invalidateCache(); + * } + * public Birthday getUserBirthday(int userId) { + * return mBirthdayCache.query(userId); + * } + * ... + * } + * </pre> + * + * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday + * for the first time; on subsequent queries, we return the already-known Birthday object. + * + * The second parameter to the IpcDataCache constructor is a string that identifies the "module" + * that owns the cache. There are some well-known modules (such as {@code MODULE_SYSTEM} but any + * string is permitted. The third parameters is the name of the API being cached; this, too, can + * any value. The fourth is the name of the cache. The cache is usually named after th API. + * Some things you must know about the three strings: + * <list> + * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}. + * Usually, the SELinux rules permit a process to write a system property (and therefore + * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that + * although the cache can be constructed with any module string, whatever string is chosen must be + * consistent with the SELinux configuration. + * <ul> The API name can be any string of alphanumeric characters. All caches with the same API + * are invalidated at the same time. If a server supports several caches and all are invalidated + * in common, then it is most efficient to assign the same API string to every cache. + * <ul> The cache name can be any string. In debug output, the name is used to distiguish between + * caches with the same API name. The cache name is also used when disabling caches in the + * current process. So, invalidation is based on the module+api but disabling (which is generally + * a once-per-process operation) is based on the cache name. + * </list> + * + * User birthdays do occasionally change, so we have to modify the server to invalidate this + * cache when necessary. That invalidation code looks like this: + * + * <pre> + * public class UserBirthdayServiceImpl { + * ... + * public UserBirthdayServiceImpl() { + * ... + * ActivityThread.currentActivityThread().disableUserBirthdayCache(); + * ActivityThread.currentActivityThread().invalidateUserBirthdayCache(); + * } + * + * private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) { + * mUidToBirthday.clear(); + * mUidToBirthday.putAll(uidToBirthday); + * ActivityThread.currentActivityThread().invalidateUserBirthdayCache(); + * } + * ... + * } + * </pre> + * + * The call to {@code IpcDataCache.invalidateCache()} guarantees that all clients will re-fetch + * birthdays from binder during consequent calls to + * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock + * held, we maintain consistency between different client views of the birthday state. The use of + * IpcDataCache in this idiomatic way introduces no new race conditions. + * + * IpcDataCache has a few other features for doing things like incremental enhancement of cached + * values and invalidation of multiple caches (that all share the same property key) at once. + * + * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each + * time we update the cache. SELinux configuration must allow everyone to read this property + * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write + * the property. (These properties conventionally begin with the "cache_key." prefix.) + * + * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so + * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In this + * local case, there's no IPC, so use of the cache is (depending on exact circumstance) + * unnecessary. + * + * There may be queries for which it is more efficient to bypass the cache than to cache the + * result. This would be true, for example, if some queries would require frequent cache + * invalidation while other queries require infrequent invalidation. To expand on the birthday + * example, suppose that there is a userId that signifies "the next birthday". When passed this + * userId, the server returns the next birthday among all users - this value changes as time + * advances. The userId value can be cached, but the cache must be invalidated whenever a + * birthday occurs, and this invalidates all birthdays. If there is a large number of users, + * invalidation will happen so often that the cache provides no value. + * + * The class provides a bypass mechanism to handle this situation. + * <pre> + * public class ActivityThread { + * ... + * private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery = + * new IpcDataCache.QueryHandler<Integer, Birthday>() { + * {@literal @}Override + * public Birthday apply(Integer) { + * return GetService("birthdayd").getUserBirthday(userId); + * } + * {@literal @}Override + * public boolean shouldBypassQuery(Integer userId) { + * return userId == NEXT_BIRTHDAY; + * } + * }; + * ... + * } + * </pre> + * + * If the {@code shouldBypassQuery()} method returns true then the cache is not used for that + * particular query. The {@code shouldBypassQuery()} method is not abstract and the default + * implementation returns false. + * + * For security, there is a allowlist of processes that are allowed to invalidate a cache. The + * allowlist includes normal runtime processes but does not include test processes. Test + * processes must call {@code IpcDataCache.disableForTestMode()} to disable all cache activity in + * that process. + * + * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding. + * + * To test a binder cache, create one or more tests that exercise the binder method. This should + * be done twice: once with production code and once with a special image that sets {@code DEBUG} + * and {@code VERIFY} true. In the latter case, verify that no cache inconsistencies are + * reported. If a cache inconsistency is reported, however, it might be a false positive. This + * happens if the server side data can be read and written non-atomically with respect to cache + * invalidation. + * + * @param <Query> The class used to index cache entries: must be hashable and comparable + * @param <Result> The class holding cache entries; use a boxed primitive if possible + * @hide + */ +@TestApi +@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) +public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, Result> { + /** + * {@inheritDoc} + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public static abstract class QueryHandler<Q,R> + extends PropertyInvalidatedCache.QueryHandler<Q,R> { + /** + * Compute a result given a query. The semantics are those of Functor. + */ + public abstract @Nullable R apply(@NonNull Q query); + + /** + * Return true if a query should not use the cache. The default implementation + * always uses the cache. + */ + public boolean shouldBypassCache(@NonNull Q query) { + return false; + } + }; + + /** + * The module used for unit tests and cts tests. It is expected that no process in + * the system has permissions to write properties with this module. + * @hide + */ + @TestApi + public static final String MODULE_TEST = PropertyInvalidatedCache.MODULE_TEST; + + /** + * The module used for system server/framework caches. This is not visible outside + * the system processes. + * @hide + */ + @TestApi + public static final String MODULE_SYSTEM = PropertyInvalidatedCache.MODULE_SYSTEM; + + /** + * The module used for bluetooth caches. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public static final String MODULE_BLUETOOTH = PropertyInvalidatedCache.MODULE_BLUETOOTH; + + /** + * Make a new property invalidated cache. The key is computed from the module and api + * parameters. + * + * @param maxEntries Maximum number of entries to cache; LRU discard + * @param module The module under which the cache key should be placed. + * @param api The api this cache front-ends. The api must be a Java identifier but + * need not be an actual api. + * @param cacheName Name of this cache in debug and dumpsys + * @param computer The code to compute values that are not in the cache. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public IpcDataCache(int maxEntries, @NonNull String module, @NonNull String api, + @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) { + super(maxEntries, module, api, cacheName, computer); + } + + /** + * {@inheritDoc} + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + @Override + public void disableForCurrentProcess() { + super.disableForCurrentProcess(); + } + + /** + * {@inheritDoc} + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public static void disableForCurrentProcess(@NonNull String cacheName) { + PropertyInvalidatedCache.disableForCurrentProcess(cacheName); + } + + /** + * {@inheritDoc} + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + @Override + public @Nullable Result query(@NonNull Query query) { + return super.query(query); + } + + /** + * {@inheritDoc} + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + @Override + public void invalidateCache() { + super.invalidateCache(); + } + + /** + * Invalidate caches in all processes that are keyed for the module and api. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public static void invalidateCache(@NonNull String module, @NonNull String api) { + PropertyInvalidatedCache.invalidateCache(module, api); + } +} diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 9428ac965f84..e06e7326a860 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -257,6 +257,14 @@ public class Process { public static final int UWB_UID = 1083; /** + * Defines a virtual UID that is used to aggregate data related to SDK sandbox UIDs. + * {@see SdkSandboxManager} + * @hide + */ + @TestApi + public static final int SDK_SANDBOX_VIRTUAL_UID = 1090; + + /** * GID that corresponds to the INTERNET permission. * Must match the value of AID_INET. * @hide @@ -920,7 +928,7 @@ public class Process { */ @SystemApi(client = MODULE_LIBRARIES) @TestApi - public static final int sdkSandboxToAppUid(int uid) { + public static final int getAppUidForSdkSandboxUid(int uid) { return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID); } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index b501730f1eeb..312abf877f53 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -83,7 +83,6 @@ import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; -import android.sysprop.VoldProperties; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -1739,10 +1738,7 @@ public class StorageManager { * false not encrypted or file encrypted */ public static boolean isBlockEncrypted() { - if (!isEncrypted()) { - return false; - } - return RoSystemProperties.CRYPTO_BLOCK_ENCRYPTED; + return false; } /** {@hide} @@ -1752,18 +1748,7 @@ public class StorageManager { * false not encrypted, file encrypted or default block encrypted */ public static boolean isNonDefaultBlockEncrypted() { - if (!isBlockEncrypted()) { - return false; - } - - try { - IStorageManager storageManager = IStorageManager.Stub.asInterface( - ServiceManager.getService("mount")); - return storageManager.getPasswordType() != CRYPT_TYPE_DEFAULT; - } catch (RemoteException e) { - Log.e(TAG, "Error getting encryption type"); - return false; - } + return false; } /** {@hide} @@ -1777,8 +1762,7 @@ public class StorageManager { * framework, so no service needs to check for changes during their lifespan */ public static boolean isBlockEncrypting() { - final String state = VoldProperties.encrypt_progress().orElse(""); - return !"".equalsIgnoreCase(state); + return false; } /** {@hide} @@ -1793,8 +1777,7 @@ public class StorageManager { * framework, so no service needs to check for changes during their lifespan */ public static boolean inCryptKeeperBounce() { - final String status = VoldProperties.decrypt().orElse(""); - return "trigger_restart_min_framework".equals(status); + return false; } /** {@hide} */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cfef7adb240f..a6ad5e5863df 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10729,14 +10729,6 @@ public final class Settings { "communal_mode_trusted_networks"; /** - * Setting to allow Fast Pair scans to be enabled. - * @hide - */ - @SystemApi - @Readable - public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; - - /** * Setting to store denylisted system languages by the CEC {@code <Set Menu Language>} * confirmation dialog. * diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index db622d39b785..001707d228b6 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -231,7 +231,7 @@ public class DreamService extends Service implements Window.Callback { * The default value for whether to show complications on the overlay. * @hide */ - public static final boolean DEFAULT_SHOW_COMPLICATIONS = true; + public static final boolean DEFAULT_SHOW_COMPLICATIONS = false; private final IDreamManager mDreamManager; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -1341,7 +1341,9 @@ public class DreamService extends Service implements Window.Callback { public void onViewDetachedFromWindow(View v) { if (mActivity == null || !mActivity.isChangingConfigurations()) { // Only stop the dream if the view is not detached by relaunching - // activity for configuration changes. + // activity for configuration changes. It is important to also clear + // the window reference in order to fully release the DreamActivity. + mWindow = null; mActivity = null; finish(); } diff --git a/core/java/android/service/dreams/OWNERS b/core/java/android/service/dreams/OWNERS index f8318054ddfc..489a5f62b49d 100644 --- a/core/java/android/service/dreams/OWNERS +++ b/core/java/android/service/dreams/OWNERS @@ -1,3 +1,8 @@ # Bug component: 78010 +brycelee@google.com dsandler@google.com +galinap@google.com +jjaggi@google.com +michaelwr@google.com +santoscordon@google.com diff --git a/core/java/android/service/games/GameScreenshotResult.java b/core/java/android/service/games/GameScreenshotResult.java index ae76e08c7971..5490aef0e225 100644 --- a/core/java/android/service/games/GameScreenshotResult.java +++ b/core/java/android/service/games/GameScreenshotResult.java @@ -18,8 +18,6 @@ package android.service.games; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; @@ -30,9 +28,7 @@ import java.util.Objects; /** * Result object for calls to {@link IGameSessionController#takeScreenshot}. * - * It includes a status (see {@link #getStatus}) and, if the status is - * {@link #GAME_SCREENSHOT_SUCCESS} an {@link android.graphics.Bitmap} result (see {@link - * #getBitmap}). + * It includes a status only (see {@link #getStatus}). * * @hide */ @@ -54,8 +50,7 @@ public final class GameScreenshotResult implements Parcelable { /** * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} was - * successful and an {@link android.graphics.Bitmap} result should be available by calling - * {@link #getBitmap}. + * successful. * * @hide */ @@ -81,9 +76,7 @@ public final class GameScreenshotResult implements Parcelable { new Parcelable.Creator<GameScreenshotResult>() { @Override public GameScreenshotResult createFromParcel(Parcel source) { - return new GameScreenshotResult( - source.readInt(), - source.readParcelable(null, Bitmap.class)); + return new GameScreenshotResult(source.readInt()); } @Override @@ -95,14 +88,11 @@ public final class GameScreenshotResult implements Parcelable { @GameScreenshotStatus private final int mStatus; - @Nullable - private final Bitmap mBitmap; - /** - * Creates a successful {@link GameScreenshotResult} with the provided bitmap. + * Creates a successful {@link GameScreenshotResult}. */ - public static GameScreenshotResult createSuccessResult(@NonNull Bitmap bitmap) { - return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS, bitmap); + public static GameScreenshotResult createSuccessResult() { + return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS); } /** @@ -110,12 +100,11 @@ public final class GameScreenshotResult implements Parcelable { * {@link #GAME_SCREENSHOT_ERROR_INTERNAL_ERROR} status. */ public static GameScreenshotResult createInternalErrorResult() { - return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, null); + return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR); } - private GameScreenshotResult(@GameScreenshotStatus int status, @Nullable Bitmap bitmap) { + private GameScreenshotResult(@GameScreenshotStatus int status) { this.mStatus = status; - this.mBitmap = bitmap; } @Override @@ -126,7 +115,6 @@ public final class GameScreenshotResult implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mStatus); - dest.writeParcelable(mBitmap, flags); } @GameScreenshotStatus @@ -134,29 +122,12 @@ public final class GameScreenshotResult implements Parcelable { return mStatus; } - /** - * Gets the {@link Bitmap} result from a successful screenshot attempt. - * - * @return The bitmap. - * @throws IllegalStateException if this method is called when {@link #getStatus} does not - * return {@link #GAME_SCREENSHOT_SUCCESS}. - */ - @NonNull - public Bitmap getBitmap() { - if (mBitmap == null) { - throw new IllegalStateException("Bitmap not available for failed screenshot result"); - } - return mBitmap; - } - @Override public String toString() { return "GameScreenshotResult{" + "mStatus=" + mStatus - + ", has bitmap='" - + mBitmap != null ? "yes" : "no" - + "\'}"; + + "}"; } @Override @@ -170,12 +141,11 @@ public final class GameScreenshotResult implements Parcelable { } GameScreenshotResult that = (GameScreenshotResult) o; - return mStatus == that.mStatus - && Objects.equals(mBitmap, that.mBitmap); + return mStatus == that.mStatus; } @Override public int hashCode() { - return Objects.hash(mStatus, mBitmap); + return Objects.hash(mStatus); } } diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index 468e087c941b..1548f5224572 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -29,7 +29,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; @@ -367,7 +366,7 @@ public abstract class GameSession { } /** - * Interface for returning screenshot outcome from calls to {@link #takeScreenshot}. + * Interface for handling result of {@link #takeScreenshot}. */ public interface ScreenshotCallback { @@ -402,18 +401,16 @@ public abstract class GameSession { /** * Called when taking the screenshot succeeded. - * - * @param bitmap The screenshot. */ - void onSuccess(@NonNull Bitmap bitmap); + void onSuccess(); } /** * Takes a screenshot of the associated game. For this call to succeed, the device screen * must be turned on and the game task must be visible. * - * If the callback is called with {@link ScreenshotCallback#onSuccess}, the provided {@link - * Bitmap} may be used. + * If the callback is called with {@link ScreenshotCallback#onSuccess}, the screenshot is + * taken successfully. * * If the callback is called with {@link ScreenshotCallback#onFailure}, the provided status * code should be checked. @@ -460,7 +457,7 @@ public abstract class GameSession { @GameScreenshotResult.GameScreenshotStatus int status = result.getStatus(); switch (status) { case GameScreenshotResult.GAME_SCREENSHOT_SUCCESS: - callback.onSuccess(result.getBitmap()); + callback.onSuccess(); break; case GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR: Slog.w(TAG, "Error taking screenshot"); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 4541f3afa428..fe6ae78ccf2d 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -71,8 +71,8 @@ public class FeatureFlagUtils { * Hide back key in the Settings two pane design. * @hide */ - public static final String SETTINGS_HIDE_SECONDARY_PAGE_BACK_BUTTON_IN_TWO_PANE = - "settings_hide_secondary_page_back_button_in_two_pane"; + public static final String SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE = + "settings_hide_second_layer_page_navigate_up_button_in_two_pane"; private static final Map<String, String> DEFAULT_FLAGS; @@ -99,7 +99,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false"); - DEFAULT_FLAGS.put(SETTINGS_HIDE_SECONDARY_PAGE_BACK_BUTTON_IN_TWO_PANE, "true"); + DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true"); } private static final Set<String> PERSISTENT_FLAGS; @@ -109,7 +109,7 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME); - PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECONDARY_PAGE_BACK_BUTTON_IN_TWO_PANE); + PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE); } /** diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 0ef585478346..cc93adc32541 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1877,7 +1877,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { float x, float y, float pressure, float size, int metaState, float xPrecision, float yPrecision, int deviceId, int edgeFlags) { return obtain(downTime, eventTime, action, x, y, pressure, size, metaState, - xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_UNKNOWN, + xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_CLASS_POINTER, DEFAULT_DISPLAY); } diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 69ad739608b2..fd336a27bb67 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -31,6 +31,7 @@ import android.view.MotionEvent; import android.view.View; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.InlineSuggestionsRequestInfo; @@ -105,14 +106,13 @@ public interface InputMethod { * current IME. * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME. * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME. - * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be - * shown while the IME is shown. + * @param navButtonFlags The initial state of {@link InputMethodNavButtonFlags}. * @hide */ @MainThread default void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations, int configChanges, - boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) { + boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) { attachToken(token); } @@ -231,8 +231,7 @@ public interface InputMethod { * the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as * long as your implementation of {@link InputMethod} relies on such * IPCs - * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be - * shown while the IME is shown. + * @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session. * @see #startInput(InputConnection, EditorInfo) * @see #restartInput(InputConnection, EditorInfo) * @see EditorInfo @@ -241,7 +240,7 @@ public interface InputMethod { @MainThread default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) { + @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) { if (restarting) { restartInput(inputConnection, editorInfo); } else { @@ -250,15 +249,13 @@ public interface InputMethod { } /** - * Notifies that whether the IME should show the IME switcher or not is being changed. + * Notifies that {@link InputMethodNavButtonFlags} have been updated. * - * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be - * shown while the IME is shown. + * @param navButtonFlags The new {@link InputMethodNavButtonFlags}. * @hide */ @MainThread - default void onShouldShowImeSwitcherWhenImeIsShownChanged( - boolean shouldShowImeSwitcherWhenImeIsShown) { + default void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { } /** diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl index 022d05da8825..172456e4d0fa 100644 --- a/core/java/android/window/ITaskOrganizerController.aidl +++ b/core/java/android/window/ITaskOrganizerController.aidl @@ -52,7 +52,7 @@ interface ITaskOrganizerController { /** Gets all root tasks on a display (ordered from top-to-bottom) */ List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes); - /** Get the root task which contains the current ime target */ + /** Get the {@link WindowContainerToken} of the task which contains the current ime target */ WindowContainerToken getImeTarget(int display); /** diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 3ec18dbe0ebc..8d88f80ff483 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -199,7 +199,7 @@ public class TaskOrganizer extends WindowOrganizer { } } - /** Get the root task which contains the current ime target */ + /** Get the {@link WindowContainerToken} of the task which contains the current ime target */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) @Nullable public WindowContainerToken getImeTarget(int display) { diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 5f82fb00ed75..5dbb551da681 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -28,6 +28,7 @@ import android.util.Log; import android.view.IWindow; import android.view.IWindowSession; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.TreeMap; @@ -185,35 +186,58 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } private static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { - private final OnBackInvokedCallback mCallback; + private final WeakReference<OnBackInvokedCallback> mCallback; OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) { - mCallback = callback; - } - - @NonNull - public OnBackInvokedCallback getCallback() { - return mCallback; + mCallback = new WeakReference<>(callback); } @Override public void onBackStarted() { - Handler.getMain().post(() -> mCallback.onBackStarted()); + Handler.getMain().post(() -> { + final OnBackInvokedCallback callback = mCallback.get(); + if (callback == null) { + return; + } + + callback.onBackStarted(); + }); } @Override public void onBackProgressed(BackEvent backEvent) { - Handler.getMain().post(() -> mCallback.onBackProgressed(backEvent)); + Handler.getMain().post(() -> { + final OnBackInvokedCallback callback = mCallback.get(); + if (callback == null) { + return; + } + + callback.onBackProgressed(backEvent); + }); } @Override public void onBackCancelled() { - Handler.getMain().post(() -> mCallback.onBackCancelled()); + Handler.getMain().post(() -> { + final OnBackInvokedCallback callback = mCallback.get(); + if (callback == null) { + return; + } + + callback.onBackCancelled(); + }); } @Override public void onBackInvoked() throws RemoteException { - Handler.getMain().post(() -> mCallback.onBackInvoked()); + Handler.getMain().post(() -> { + final OnBackInvokedCallback callback = mCallback.get(); + if (callback == null) { + return; + } + + callback.onBackInvoked(); + }); } } diff --git a/core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java b/core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java new file mode 100644 index 000000000000..728a42907cc4 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputMethodNavButtonFlags.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; + +/** + * A set of flags notified from {@link com.android.server.inputmethod.InputMethodManagerService} to + * {@link android.inputmethodservice.InputMethodService} regarding how + * {@link android.inputmethodservice.NavigationBarController} should behave. + * + * <p>These flags will take effect when and only when + * {@link android.inputmethodservice.InputMethodService#canImeRenderGesturalNavButtons} returns + * {@code true}.</p> + */ +@Retention(SOURCE) +@IntDef(flag = true, value = { + InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR, + InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN, +}) +public @interface InputMethodNavButtonFlags { + /** + * When set, the IME process needs to render and handle the navigation bar buttons such as the + * back button and the IME switcher button. + */ + int IME_DRAWS_IME_NAV_BAR = 1; + /** + * When set, the IME switcher icon needs to be shown on the navigation bar. + */ + int SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN = 2; +} diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 3746bfdae781..5947e66b2d30 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -62,6 +62,10 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; @@ -176,6 +180,10 @@ public class InteractionJankMonitor { public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42; public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43; public static final int CUJ_UNFOLD_ANIM = 44; + public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45; + public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46; + public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47; + public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48; private static final int NO_STATSD_LOGGING = -1; @@ -229,6 +237,10 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS, }; private static volatile InteractionJankMonitor sInstance; @@ -294,6 +306,10 @@ public class InteractionJankMonitor { CUJ_ONE_HANDED_ENTER_TRANSITION, CUJ_ONE_HANDED_EXIT_TRANSITION, CUJ_UNFOLD_ANIM, + CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS, + CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS, + CUJ_SUW_LOADING_TO_NEXT_FLOW, + CUJ_SUW_LOADING_SCREEN_FOR_STATUS }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -701,6 +717,14 @@ public class InteractionJankMonitor { return "ONE_HANDED_EXIT_TRANSITION"; case CUJ_UNFOLD_ANIM: return "UNFOLD_ANIM"; + case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS: + return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS"; + case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS: + return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS"; + case CUJ_SUW_LOADING_TO_NEXT_FLOW: + return "SUW_LOADING_TO_NEXT_FLOW"; + case CUJ_SUW_LOADING_SCREEN_FOR_STATUS: + return "SUW_LOADING_SCREEN_FOR_STATUS"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 7ee1f17b34df..74749cc9bf0b 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -100,7 +100,7 @@ public class TransitionAnimation { // TODO (b/215515255): remove once we full migrate to shell transitions private static final boolean SHELL_TRANSITIONS_ENABLED = - SystemProperties.getBoolean("persist.debug.shell_transit", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); private final Context mContext; private final String mTag; diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 9163b6d6215e..1d60c501a88b 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -70,7 +70,7 @@ interface IStatusBarService void onPanelRevealed(boolean clearNotificationEffects, int numItems); void onPanelHidden(); // Mark current notifications as "seen" and stop ringing, vibrating, blinking. - void clearNotificationEffects(); + oneway void clearNotificationEffects(); void onNotificationClick(String key, in NotificationVisibility nv); void onNotificationActionClick(String key, int actionIndex, in Notification.Action action, in NotificationVisibility nv, boolean generatedByAssistant); void onNotificationError(String pkg, String tag, int id, diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index d3c3917cd791..f4f438b1f601 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -11,8 +11,12 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.graphics.Bitmap; +import android.graphics.ColorSpace; import android.graphics.Insets; +import android.graphics.ParcelableColorSpace; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -26,6 +30,7 @@ import android.os.UserHandle; import android.util.Log; import android.view.WindowManager; +import java.util.Objects; import java.util.function.Consumer; public class ScreenshotHelper { @@ -154,6 +159,72 @@ public class ScreenshotHelper { }; } + /** + * Bundler used to convert between a hardware bitmap and a bundle without copying the internal + * content. This is expected to be used together with {@link #provideScreenshot} to handle a + * hardware bitmap as a screenshot. + */ + public static final class HardwareBitmapBundler { + private static final String KEY_BUFFER = "bitmap_util_buffer"; + private static final String KEY_COLOR_SPACE = "bitmap_util_color_space"; + + private HardwareBitmapBundler() { + } + + /** + * Creates a Bundle that represents the given Bitmap. + * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid + * copies when passing across processes, only pass to processes you trust. + * + * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the + * returned Bundle should be treated as a standalone object. + * + * @param bitmap to convert to bundle + * @return a Bundle representing the bitmap, should only be parsed by + * {@link #bundleToHardwareBitmap(Bundle)} + */ + public static Bundle hardwareBitmapToBundle(Bitmap bitmap) { + if (bitmap.getConfig() != Bitmap.Config.HARDWARE) { + throw new IllegalArgumentException( + "Passed bitmap must have hardware config, found: " + bitmap.getConfig()); + } + + // Bitmap assumes SRGB for null color space + ParcelableColorSpace colorSpace = + bitmap.getColorSpace() == null + ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)) + : new ParcelableColorSpace(bitmap.getColorSpace()); + + Bundle bundle = new Bundle(); + bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer()); + bundle.putParcelable(KEY_COLOR_SPACE, colorSpace); + + return bundle; + } + + /** + * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .} + * + * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing + * this + * Bitmap on to any other source. + * + * @param bundle containing the bitmap + * @return a hardware Bitmap + */ + public static Bitmap bundleToHardwareBitmap(Bundle bundle) { + if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) { + throw new IllegalArgumentException("Bundle does not contain a hardware bitmap"); + } + + HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER); + ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE); + + return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer), + colorSpace.getColorSpace()); + } + } + private static final String TAG = "ScreenshotHelper"; // Time until we give up on the screenshot & show an error instead. diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index d2bc3442ed36..273c5f170812 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -37,8 +37,7 @@ import com.android.internal.view.InlineSuggestionsRequestInfo; */ oneway interface IInputMethod { void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges, boolean stylusHwSupported, - boolean shouldShowImeSwitcherWhenImeIsShown); + int configChanges, boolean stylusHwSupported, int navigationBarFlags); void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo, in IInlineSuggestionsRequestCallback cb); @@ -48,10 +47,9 @@ oneway interface IInputMethod { void unbindInput(); void startInput(in IBinder startInputToken, in IInputContext inputContext, - in EditorInfo attribute, boolean restarting, - boolean shouldShowImeSwitcherWhenImeIsShown); + in EditorInfo attribute, boolean restarting, int navigationBarFlags); - void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown); + void onNavButtonFlagsChanged(int navButtonFlags); void createSession(in InputChannel channel, IInputSessionCallback callback); diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index db4bc2c7e24a..851e8e0f3269 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -97,7 +97,6 @@ interface ILockSettings { boolean hasSecureLockScreen(); boolean tryUnlockWithCachedUnifiedChallenge(int userId); void removeCachedUnifiedChallenge(int userId); - void updateEncryptionPassword(int type, in byte[] password); boolean registerWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener); boolean unregisterWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener); long addWeakEscrowToken(in byte[] token, int userId, in IWeakEscrowTokenActivatedListener callback); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 1e11c6d1fddb..82ae42441032 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -722,38 +722,14 @@ public class LockPatternUtils { return true; } - private void updateCryptoUserInfo(int userId) { - if (userId != UserHandle.USER_SYSTEM) { - return; - } - - final String ownerInfo = isOwnerInfoEnabled(userId) ? getOwnerInfo(userId) : ""; - - IBinder service = ServiceManager.getService("mount"); - if (service == null) { - Log.e(TAG, "Could not find the mount service to update the user info"); - return; - } - - IStorageManager storageManager = IStorageManager.Stub.asInterface(service); - try { - Log.d(TAG, "Setting owner info"); - storageManager.setField(StorageManager.OWNER_INFO_KEY, ownerInfo); - } catch (RemoteException e) { - Log.e(TAG, "Error changing user info", e); - } - } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setOwnerInfo(String info, int userId) { setString(LOCK_SCREEN_OWNER_INFO, info, userId); - updateCryptoUserInfo(userId); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setOwnerInfoEnabled(boolean enabled, int userId) { setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled, userId); - updateCryptoUserInfo(userId); } @UnsupportedAppUsage @@ -808,17 +784,6 @@ public class LockPatternUtils { } /** - * Clears the encryption password. - */ - public void clearEncryptionPassword() { - try { - getLockSettings().updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't clear encryption password"); - } - } - - /** * Retrieves the quality mode for {@code userHandle}. * @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName) * @@ -1042,24 +1007,6 @@ public class LockPatternUtils { */ public void setVisiblePatternEnabled(boolean enabled, int userId) { setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled, userId); - - // Update for crypto if owner - if (userId != UserHandle.USER_SYSTEM) { - return; - } - - IBinder service = ServiceManager.getService("mount"); - if (service == null) { - Log.e(TAG, "Could not find the mount service to update the user info"); - return; - } - - IStorageManager storageManager = IStorageManager.Stub.asInterface(service); - try { - storageManager.setField(StorageManager.PATTERN_VISIBLE_KEY, enabled ? "1" : "0"); - } catch (RemoteException e) { - Log.e(TAG, "Error changing pattern visible state", e); - } } public boolean isVisiblePatternEverChosen(int userId) { @@ -1070,23 +1017,7 @@ public class LockPatternUtils { * Set whether the visible password is enabled for cryptkeeper screen. */ public void setVisiblePasswordEnabled(boolean enabled, int userId) { - // Update for crypto if owner - if (userId != UserHandle.USER_SYSTEM) { - return; - } - - IBinder service = ServiceManager.getService("mount"); - if (service == null) { - Log.e(TAG, "Could not find the mount service to update the user info"); - return; - } - - IStorageManager storageManager = IStorageManager.Stub.asInterface(service); - try { - storageManager.setField(StorageManager.PASSWORD_VISIBLE_KEY, enabled ? "1" : "0"); - } catch (RemoteException e) { - Log.e(TAG, "Error changing password visible state", e); - } + // No longer does anything. } /** diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 2b6b933c6886..01cec7727c4e 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -45,6 +45,7 @@ import android.util.AttributeSet; import android.util.IntArray; import android.util.Log; import android.util.SparseArray; +import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.RenderNodeAnimator; @@ -82,10 +83,12 @@ public class LockPatternView extends View { private static final int DOT_ACTIVATION_DURATION_MILLIS = 50; private static final int DOT_RADIUS_INCREASE_DURATION_MILLIS = 96; private static final int DOT_RADIUS_DECREASE_DURATION_MILLIS = 192; + private static final float MIN_DOT_HIT_FACTOR = 0.2f; private final CellState[][] mCellStates; private final int mDotSize; private final int mDotSizeActivated; + private final float mDotHitFactor; private final int mPathWidth; private boolean mDrawingProfilingStarted = false; @@ -143,12 +146,11 @@ public class LockPatternView extends View { private boolean mPatternInProgress = false; private boolean mFadePattern = true; - private float mHitFactor = 0.6f; - @UnsupportedAppUsage private float mSquareWidth; @UnsupportedAppUsage private float mSquareHeight; + private float mDotHitRadius; private final LinearGradient mFadeOutGradientShader; private final Path mCurrentPath = new Path(); @@ -164,8 +166,7 @@ public class LockPatternView extends View { private final Interpolator mFastOutSlowInInterpolator; private final Interpolator mLinearOutSlowInInterpolator; - private PatternExploreByTouchHelper mExploreByTouchHelper; - private AudioManager mAudioManager; + private final PatternExploreByTouchHelper mExploreByTouchHelper; private Drawable mSelectedDrawable; private Drawable mNotSelectedDrawable; @@ -349,6 +350,9 @@ public class LockPatternView extends View { mDotSize = getResources().getDimensionPixelSize(R.dimen.lock_pattern_dot_size); mDotSizeActivated = getResources().getDimensionPixelSize( R.dimen.lock_pattern_dot_size_activated); + TypedValue outValue = new TypedValue(); + getResources().getValue(R.dimen.lock_pattern_dot_hit_factor, outValue, true); + mDotHitFactor = Math.max(Math.min(outValue.getFloat(), 1f), MIN_DOT_HIT_FACTOR); mUseLockPatternDrawable = getResources().getBoolean(R.bool.use_lock_pattern_drawable); if (mUseLockPatternDrawable) { @@ -375,7 +379,6 @@ public class LockPatternView extends View { AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); mExploreByTouchHelper = new PatternExploreByTouchHelper(this); setAccessibilityDelegate(mExploreByTouchHelper); - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); int fadeAwayGradientWidth = getResources().getDimensionPixelSize( R.dimen.lock_pattern_fade_away_gradient_width); @@ -679,6 +682,7 @@ public class LockPatternView extends View { final int height = h - mPaddingTop - mPaddingBottom; mSquareHeight = height / 3.0f; mExploreByTouchHelper.invalidateRoot(); + mDotHitRadius = Math.min(mSquareHeight / 2, mSquareWidth / 2) * mDotHitFactor; if (mUseLockPatternDrawable) { mNotSelectedDrawable.setBounds(mPaddingLeft, mPaddingTop, width, height); @@ -890,63 +894,30 @@ public class LockPatternView extends View { return set; } - // helper method to find which cell a point maps to + @Nullable private Cell checkForNewHit(float x, float y) { - - final int rowHit = getRowHit(y); - if (rowHit < 0) { - return null; - } - final int columnHit = getColumnHit(x); - if (columnHit < 0) { - return null; - } - - if (mPatternDrawLookup[rowHit][columnHit]) { - return null; - } - return Cell.of(rowHit, columnHit); - } - - /** - * Helper method to find the row that y falls into. - * @param y The y coordinate - * @return The row that y falls in, or -1 if it falls in no row. - */ - private int getRowHit(float y) { - - final float squareHeight = mSquareHeight; - float hitSize = squareHeight * mHitFactor; - - float offset = mPaddingTop + (squareHeight - hitSize) / 2f; - for (int i = 0; i < 3; i++) { - - final float hitTop = offset + squareHeight * i; - if (y >= hitTop && y <= hitTop + hitSize) { - return i; - } + Cell cellHit = detectCellHit(x, y); + if (cellHit != null && !mPatternDrawLookup[cellHit.row][cellHit.column]) { + return cellHit; } - return -1; + return null; } - /** - * Helper method to find the column x fallis into. - * @param x The x coordinate. - * @return The column that x falls in, or -1 if it falls in no column. - */ - private int getColumnHit(float x) { - final float squareWidth = mSquareWidth; - float hitSize = squareWidth * mHitFactor; - - float offset = mPaddingLeft + (squareWidth - hitSize) / 2f; - for (int i = 0; i < 3; i++) { - - final float hitLeft = offset + squareWidth * i; - if (x >= hitLeft && x <= hitLeft + hitSize) { - return i; + /** Helper method to find which cell a point maps to. */ + @Nullable + private Cell detectCellHit(float x, float y) { + final float hitRadiusSquared = mDotHitRadius * mDotHitRadius; + for (int row = 0; row < 3; row++) { + for (int column = 0; column < 3; column++) { + float centerY = getCenterYForRow(row); + float centerX = getCenterXForColumn(column); + if ((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY) + < hitRadiusSquared) { + return Cell.of(row, column); + } } } - return -1; + return null; } @Override @@ -1553,8 +1524,7 @@ public class LockPatternView extends View { protected int getVirtualViewAt(float x, float y) { // This must use the same hit logic for the screen to ensure consistency whether // accessibility is on or off. - int id = getVirtualViewIdForHit(x, y); - return id; + return getVirtualViewIdForHit(x, y); } @Override @@ -1670,12 +1640,11 @@ public class LockPatternView extends View { final int col = ordinal % 3; float centerX = getCenterXForColumn(col); float centerY = getCenterYForRow(row); - float cellheight = mSquareHeight * mHitFactor * 0.5f; - float cellwidth = mSquareWidth * mHitFactor * 0.5f; - bounds.left = (int) (centerX - cellwidth); - bounds.right = (int) (centerX + cellwidth); - bounds.top = (int) (centerY - cellheight); - bounds.bottom = (int) (centerY + cellheight); + float cellHitRadius = mDotHitRadius; + bounds.left = (int) (centerX - cellHitRadius); + bounds.right = (int) (centerX + cellHitRadius); + bounds.top = (int) (centerY - cellHitRadius); + bounds.bottom = (int) (centerY + cellHitRadius); return bounds; } @@ -1694,16 +1663,12 @@ public class LockPatternView extends View { * @return VIRTUAL_BASE_VIEW_ID+id or 0 if no view was hit */ private int getVirtualViewIdForHit(float x, float y) { - final int rowHit = getRowHit(y); - if (rowHit < 0) { - return ExploreByTouchHelper.INVALID_ID; - } - final int columnHit = getColumnHit(x); - if (columnHit < 0) { + Cell cellHit = detectCellHit(x, y); + if (cellHit == null) { return ExploreByTouchHelper.INVALID_ID; } - boolean dotAvailable = mPatternDrawLookup[rowHit][columnHit]; - int dotId = (rowHit * 3 + columnHit) + VIRTUAL_BASE_VIEW_ID; + boolean dotAvailable = mPatternDrawLookup[cellHit.row][cellHit.column]; + int dotId = (cellHit.row * 3 + cellHit.column) + VIRTUAL_BASE_VIEW_ID; int view = dotAvailable ? dotId : ExploreByTouchHelper.INVALID_ID; if (DEBUG_A11Y) Log.v(TAG, "getVirtualViewIdForHit(" + x + "," + y + ") => " + view + "avail =" + dotAvailable); diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 15a6bc4369b3..57026d95ceb8 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -62,7 +62,7 @@ import "frameworks/base/core/proto/android/privacy.proto"; import "frameworks/base/core/proto/android/section.proto"; import "frameworks/base/proto/src/ipconnectivity.proto"; import "packages/modules/Connectivity/framework/proto/netstats.proto"; -import "packages/modules/Permission/service/proto/com/android/role/roleservice.proto"; +import "packages/modules/Permission/service/proto/role_service.proto"; package android.os; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fa9eba34d30b..becac7f96428 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1901,11 +1901,21 @@ <!-- Allows applications to enable/disable wifi auto join. This permission is used to let OEMs grant their trusted app access to a subset of privileged wifi APIs to improve wifi performance. - <p>Not for use by third-party applications. --> + <p>Not for use by third-party applications. + @deprecated will be replaced with MANAGE_WIFI_NETWORK_SELECTION --> <permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN" android:protectionLevel="signature|privileged|knownSigner" android:knownCerts="@array/wifi_known_signers" /> + <!-- This permission is used to let OEMs grant their trusted app access to a subset of + privileged wifi APIs to improve wifi performance. Allows applications to manage + Wi-Fi network selection related features such as enable or disable global auto-join, + modify connectivity scan intervals, and approve Wi-Fi Direct connections. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MANAGE_WIFI_NETWORK_SELECTION" + android:protectionLevel="signature|privileged|knownSigner" + android:knownCerts="@array/wifi_known_signers" /> + <!-- Allows applications to get notified when a Wi-Fi interface request cannot be satisfied without tearing down one or more other interfaces, and provide a decision whether to approve the request or reject it. @@ -3089,7 +3099,7 @@ <p>Not for use by third-party applications. --> <permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" - android:protectionLevel="signature|recents|role"/> + android:protectionLevel="signature|recents|role|installer"/> <!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} @hide @@ -6668,10 +6678,9 @@ android:exported="false"> </activity> - <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity" - android:theme="@style/Theme.Dialog.Confirmation" + <activity android:name="com.android.server.logcat.LogAccessDialogActivity" + android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight" android:excludeFromRecents="true" - android:process=":ui" android:label="@string/log_access_confirmation_title" android:exported="false"> </activity> @@ -6830,6 +6839,13 @@ </intent-filter> </receiver> + <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver" + android:exported="false"> + <intent-filter> + <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/> + </intent-filter> + </receiver> + <service android:name="android.hardware.location.GeofenceHardwareService" android:permission="android.permission.LOCATION_HARDWARE" android:exported="false" /> diff --git a/core/res/res/drawable/grant_permissions_buttons_bottom.xml b/core/res/res/drawable/grant_permissions_buttons_bottom.xml new file mode 100644 index 000000000000..a800f15b297e --- /dev/null +++ b/core/res/res/drawable/grant_permissions_buttons_bottom.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@android:color/system_accent1_100"/> + <corners android:topLeftRadius="4dp" android:topRightRadius="4dp" + android:bottomLeftRadius="12dp" android:bottomRightRadius="12dp"/> +</shape>
\ No newline at end of file diff --git a/core/res/res/drawable/grant_permissions_buttons_top.xml b/core/res/res/drawable/grant_permissions_buttons_top.xml new file mode 100644 index 000000000000..2bf803e340ec --- /dev/null +++ b/core/res/res/drawable/grant_permissions_buttons_top.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@android:color/system_accent1_100"/> + <corners android:topLeftRadius="12dp" android:topRightRadius="12dp" + android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp"/> +</shape>
\ No newline at end of file diff --git a/core/res/res/layout/log_access_user_consent_dialog_permission.xml b/core/res/res/layout/log_access_user_consent_dialog_permission.xml new file mode 100644 index 000000000000..bd7efbd59c33 --- /dev/null +++ b/core/res/res/layout/log_access_user_consent_dialog_permission.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2022, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="380dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:paddingLeft="24dp" + android:paddingRight="24dp" + android:paddingTop="24dp" + android:paddingBottom="24dp" + android:background="?attr/colorSurface"> + + <ImageView + android:id="@+id/log_access_image_view" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginBottom="16dp" + android:src="@drawable/ic_doc_document" + tools:layout_editor_absoluteX="148dp" + tools:layout_editor_absoluteY="35dp" + android:gravity="center" /> + + + <TextView + android:id="@+id/log_access_dialog_title" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginBottom="32dp" + android:text="@string/log_access_confirmation_title" + android:textAppearance="?attr/textAppearanceLarge" + android:textColor="@android:color/system_neutral1_900" + android:gravity="center" /> + + <TextView + android:id="@+id/log_access_dialog_body" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/log_access_confirmation_body" + android:textAppearance="@style/PrimaryAllowLogAccess" + android:gravity="center" /> + + <Button + android:id="@+id/log_access_dialog_allow_button" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="@string/log_access_confirmation_allow" + style="@style/PermissionGrantButtonTop" + android:layout_marginBottom="5dp" + android:layout_centerHorizontal="true" + android:layout_alignParentTop="true" + android:layout_alignParentBottom="true" + android:clipToOutline="true" + android:gravity="center" /> + + <Button + android:id="@+id/log_access_dialog_deny_button" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="@string/log_access_confirmation_deny" + style="@style/PermissionGrantButtonBottom" + android:layout_centerHorizontal="true" + android:layout_alignParentTop="true" + android:layout_alignParentBottom="true" + android:clipToOutline="true" + android:gravity="center" /> + +</LinearLayout>
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 879fc9e6ceb7..0a572be35023 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2335,7 +2335,7 @@ <!-- Remote server that can provide NTP responses. --> <string translatable="false" name="config_ntpServer">time.android.com</string> <!-- Normal polling frequency in milliseconds --> - <integer name="config_ntpPollingInterval">86400000</integer> + <integer name="config_ntpPollingInterval">64800000</integer> <!-- Try-again polling interval in milliseconds, in case the network request failed --> <integer name="config_ntpPollingIntervalShorter">60000</integer> <!-- Number of times to try again with the shorter interval, before backing @@ -2723,7 +2723,7 @@ <string name="config_bandwidthEstimateSource">bandwidth_estimator</string> <!-- Whether force to enable telephony new data stack or not --> - <bool name="config_force_enable_telephony_new_data_stack">false</bool> + <bool name="config_force_enable_telephony_new_data_stack">true</bool> <!-- Whether WiFi display is supported by this device. There are many prerequisites for this feature to work correctly. diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 1b9f7feec0ef..44c551259375 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -668,6 +668,9 @@ <dimen name="lock_pattern_dot_line_width">22dp</dimen> <dimen name="lock_pattern_dot_size">14dp</dimen> <dimen name="lock_pattern_dot_size_activated">30dp</dimen> + <!-- How much of the cell space is classified as hit areas [0..1] where 1 means that hit area is + a circle with diameter equals to cell minimum side min(width, height). --> + <item type="dimen" format="float" name="lock_pattern_dot_hit_factor">0.6</item> <!-- Width of a gradient applied to a lock pattern line while its disappearing animation. --> <dimen name="lock_pattern_fade_away_gradient_width">8dp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public-final.xml index 80664eb296af..19a484293b0a 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public-final.xml @@ -3222,149 +3222,4 @@ <public type="id" name="accessibilityActionDragDrop" id="0x01020056" /> <public type="id" name="accessibilityActionDragCancel" id="0x01020057" /> - <!-- =============================================================== - Resources added in version T of the platform - - NOTE: add <public> elements within a <staging-public-group> like so: - - <staging-public-group type="attr" first-id="0x01ff0000"> - <public name="exampleAttr1" /> - <public name="exampleAttr2" /> - </staging-public-group> - - To add a new <staging-public-group> block, find the id value for the - last <staging-public-group> block defined for thie API level, and - subtract 0x00010000 from it to get to the id of the new block. - - For example, if the block closest to the end of this file has an id - of 0x01ee0000, the id of the new block should be 0x01ed0000 - (0x01ee0000 - 0x00010000 = 0x01ed0000). - =============================================================== --> - <eat-comment /> - - <staging-public-group type="attr" first-id="0x01df0000"> - <public name="sharedUserMaxSdkVersion" /> - <public name="requiredSplitTypes" /> - <public name="splitTypes" /> - <public name="canDisplayOnRemoteDevices" /> - <public name="supportedTypes" /> - <public name="resetEnabledSettingsOnAppDataCleared" /> - <public name="supportsStylusHandwriting" /> - <public name="showClockAndComplications" /> - <!-- @hide @SystemApi --> - <public name="gameSessionService" /> - <public name="supportsBatteryGameMode" /> - <public name="supportsPerformanceGameMode" /> - <public name="allowGameAngleDriver" /> - <public name="allowGameDownscaling" /> - <public name="allowGameFpsOverride" /> - <public name="localeConfig" /> - <public name="showBackground" /> - <public name="useTargetActivityForQuickAccess"/> - <public name="inheritKeyStoreKeys" /> - <public name="preferKeepClear" /> - <public name="autoHandwritingEnabled" /> - <public name="fromExtendLeft" /> - <public name="fromExtendTop" /> - <public name="fromExtendRight" /> - <public name="fromExtendBottom" /> - <public name="toExtendLeft" /> - <public name="toExtendTop" /> - <public name="toExtendRight" /> - <public name="toExtendBottom" /> - <public name="tileService" /> - <public name="windowSplashScreenBehavior" /> - <public name="allowUntrustedActivityEmbedding" /> - <public name="knownActivityEmbeddingCerts" /> - <public name="intro" /> - <public name="enableOnBackInvokedCallback" /> - <public name="supportsInlineSuggestionsWithTouchExploration" /> - <public name="lineBreakStyle" /> - <public name="lineBreakWordStyle" /> - </staging-public-group> - - <staging-public-group type="id" first-id="0x01de0000"> - <public name="removed_accessibilityActionSwipeLeft" /> - <public name="removed_accessibilityActionSwipeRight" /> - <public name="removed_accessibilityActionSwipeUp" /> - <public name="removed_accessibilityActionSwipeDown" /> - <public name="accessibilityActionShowTextSuggestions" /> - <public name="inputExtractAction" /> - <public name="inputExtractAccessories" /> - </staging-public-group> - - <staging-public-group type="style" first-id="0x01dd0000"> - <public name="TextAppearance.DeviceDefault.Headline" /> - </staging-public-group> - - <staging-public-group type="string" first-id="0x01dc0000"> - <!-- @hide @SystemApi --> - <public name="config_systemSupervision" /> - <!-- @hide @SystemApi --> - <public name="config_devicePolicyManagement" /> - <!-- @hide @SystemApi --> - <public name="config_systemAppProtectionService" /> - <!-- @hide @SystemApi @TestApi --> - <public name="config_systemAutomotiveCalendarSyncManager" /> - <!-- @hide @SystemApi --> - <public name="config_defaultAutomotiveNavigation" /> - </staging-public-group> - - <staging-public-group type="dimen" first-id="0x01db0000"> - </staging-public-group> - - <staging-public-group type="color" first-id="0x01da0000"> - </staging-public-group> - - <staging-public-group type="array" first-id="0x01d90000"> - <!-- @hide @SystemApi --> - <public name="config_optionalIpSecAlgorithms" /> - </staging-public-group> - - <staging-public-group type="drawable" first-id="0x01d80000"> - </staging-public-group> - - <staging-public-group type="layout" first-id="0x01d70000"> - </staging-public-group> - - <staging-public-group type="anim" first-id="0x01d60000"> - </staging-public-group> - - <staging-public-group type="animator" first-id="0x01d50000"> - </staging-public-group> - - <staging-public-group type="interpolator" first-id="0x01d40000"> - </staging-public-group> - - <staging-public-group type="mipmap" first-id="0x01d30000"> - </staging-public-group> - - <staging-public-group type="integer" first-id="0x01d20000"> - </staging-public-group> - - <staging-public-group type="transition" first-id="0x01d10000"> - </staging-public-group> - - <staging-public-group type="raw" first-id="0x01d00000"> - </staging-public-group> - - <staging-public-group type="bool" first-id="0x01cf0000"> - <!-- @hide @TestApi --> - <public name="config_preventImeStartupUnlessTextEditor" /> - <!-- @hide @SystemApi --> - <public name="config_enableQrCodeScannerOnLockScreen" /> - </staging-public-group> - - <staging-public-group type="fraction" first-id="0x01ce0000"> - </staging-public-group> - - <!-- =============================================================== - DO NOT ADD UN-GROUPED ITEMS HERE - - Any new items (attrs, styles, ids, etc.) *must* be added in a - staging-public-group block, as the preceding comment explains. - Items added outside of a group may have their value recalculated - every time something new is added to this file. - =============================================================== --> - </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml new file mode 100644 index 000000000000..2dc17b8468c3 --- /dev/null +++ b/core/res/res/values/public-staging.xml @@ -0,0 +1,228 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Exposing a new resource: + To add a new entry, find the corresponding "staging-public-group" with the correct type for + your resource, and add a new entry to the BOTTOM of the list. This ensures that indexes + don't shift for previously added resources, and the new one will be appended to the end. + + To add R.attr.exampleAttrName: + <staging-public-group type="attr" first-id="0x1ff0000"> + <public name="previouslyAdded1"/> + <public name="previouslyAdded2"/> + <public name="exampleAttrName"/> + </staging-public-group> + + Deleting a resource: + If a resource is no longer supported/used, it can be marked removed by renaming the + resource with a `removed_` prefix. This preserves the indexes of other resources so as not + to break apps that have compiled with their integers previously. + + To remove R.attr.previouslyAdded2: + <staging-public-group type="attr" first-id="0x1ff0000"> + <public name="previouslyAdded1"/> + <public name="removed_previouslyAdded2"/> + <public name="exampleAttrName"/> + </staging-public-group> + + IMPORTANT: Deleting an entry is never allowed, even across branches or reverts. Please take + this into account before merging a change which edits this file. Small, isolated changes + which only add/remove resources is recommended to avoid reverts due to build/test failures. + + Renaming a resource: + This is generally fine and can be done to the entry directly, with no other changes. But + note that any apps/tooling that resolve against resource names rather than IDs may break + as a result. This is uncommon, but not rare. + + Finalizing a release's resources: + 1. $ANDROID_BUILD_TOP/frameworks/base/tools/aapt2/tools/finalize_res.py \ + $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-staging.xml \ + $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-final.xml + 2. Rename "NEXT" in the new public-staging.xml resources header to the next platform short + version code + + Finalizing a release's resources (manually; only for reference): + 1. Delete all "staging-public-group" blocks for the release with no entries inside them + 2. Rename the remaining "staging-public-group" blocks for that release to + "staging-public-group-final" + 3. Cut them out this file and place at the bottom of public-final.xml; also move the + "Resources added in version ? of the platform" header + 4. Copy-paste all of the non-"removed_" resources outside of the staging blocks into being + siblings alongside them + 5. Assign them final public IDs in the form of + <public type="attr" name="exampleAttrName" id="0x0101088a" /> + by finding the last ID for that type and incrementing the last 4 characters by 1 in + hexadecimal + 6. Back in this file, seed the next release's resources by adding "staging-public-group" + tags with their "first-id" value shifted by -0x00010000 from the lowest "first-id" + in the last used "staging-public-group-final" + + Example: + Starting public-staging.xml: + <!\- =============================================================== + Resources added in version ? of the platform + =============================================================== -\> + <eat-comment /> + + <staging-public-group type="attr" first-id="0x01ff0000"> + <public name="exampleAttr1"/> + <public name="removed_exampleAttr2"/> + <public name="exampleAttr3"/> + </staging-public-group> + + <staging-public-group type="id" first-id="0x01fe0000"> + </staging-public-group> + + Resulting public-final.xml: + <!\- =============================================================== + Resources added in version ? of the platform + =============================================================== -\> + <eat-comment /> + + <staging-public-group-final type="attr" first-id="0x01ff0000"> + <public name="exampleAttr1"/> + <public name="removed_exampleAttr2"/> + <public name="exampleAttr3"/> + </staging-public-group-final> + + <public type="id" name="exampleAttr1" id="0x0101088a"/> + <public type="id" name="exampleAttr3" id="0x0101088b"/> + + Resulting public-staging.xml: + <!\- =============================================================== + Resources added in version (? + 1) of the platform + =============================================================== -\> + <eat-comment /> + + <staging-public-group type="attr" first-id="0x01fd0000"> + </staging-public-group> + + <staging-public-group type="id" first-id="0x01fc0000"> + </staging-public-group> +--> +<resources> + + <!-- =============================================================== + Resources added in version T of the platform + + NOTE: After this version of the platform is forked, changes cannot be made to the root + branch's groups for that release. Only merge changes to the forked platform branch. + =============================================================== --> + <eat-comment/> + + <staging-public-group type="attr" first-id="0x01df0000"> + <public name="sharedUserMaxSdkVersion" /> + <public name="requiredSplitTypes" /> + <public name="splitTypes" /> + <public name="canDisplayOnRemoteDevices" /> + <public name="supportedTypes" /> + <public name="resetEnabledSettingsOnAppDataCleared" /> + <public name="supportsStylusHandwriting" /> + <public name="showClockAndComplications" /> + <!-- @hide @SystemApi --> + <public name="gameSessionService" /> + <public name="supportsBatteryGameMode" /> + <public name="supportsPerformanceGameMode" /> + <public name="allowGameAngleDriver" /> + <public name="allowGameDownscaling" /> + <public name="allowGameFpsOverride" /> + <public name="localeConfig" /> + <public name="showBackground" /> + <public name="useTargetActivityForQuickAccess"/> + <public name="inheritKeyStoreKeys" /> + <public name="preferKeepClear" /> + <public name="autoHandwritingEnabled" /> + <public name="fromExtendLeft" /> + <public name="fromExtendTop" /> + <public name="fromExtendRight" /> + <public name="fromExtendBottom" /> + <public name="toExtendLeft" /> + <public name="toExtendTop" /> + <public name="toExtendRight" /> + <public name="toExtendBottom" /> + <public name="tileService" /> + <public name="windowSplashScreenBehavior" /> + <public name="allowUntrustedActivityEmbedding" /> + <public name="knownActivityEmbeddingCerts" /> + <public name="intro" /> + <public name="enableOnBackInvokedCallback" /> + <public name="supportsInlineSuggestionsWithTouchExploration" /> + <public name="lineBreakStyle" /> + <public name="lineBreakWordStyle" /> + </staging-public-group> + + <staging-public-group type="id" first-id="0x01de0000"> + <public name="removed_accessibilityActionSwipeLeft" /> + <public name="removed_accessibilityActionSwipeRight" /> + <public name="removed_accessibilityActionSwipeUp" /> + <public name="removed_accessibilityActionSwipeDown" /> + <public name="accessibilityActionShowTextSuggestions" /> + <public name="inputExtractAction" /> + <public name="inputExtractAccessories" /> + </staging-public-group> + + <staging-public-group type="style" first-id="0x01dd0000"> + <public name="TextAppearance.DeviceDefault.Headline" /> + </staging-public-group> + + <staging-public-group type="string" first-id="0x01dc0000"> + <!-- @hide @SystemApi --> + <public name="config_systemSupervision" /> + <!-- @hide @SystemApi --> + <public name="config_devicePolicyManagement" /> + <!-- @hide @SystemApi --> + <public name="config_systemAppProtectionService" /> + <!-- @hide @SystemApi @TestApi --> + <public name="config_systemAutomotiveCalendarSyncManager" /> + <!-- @hide @SystemApi --> + <public name="config_defaultAutomotiveNavigation" /> + </staging-public-group> + + <staging-public-group type="dimen" first-id="0x01db0000"> + </staging-public-group> + + <staging-public-group type="color" first-id="0x01da0000"> + </staging-public-group> + + <staging-public-group type="array" first-id="0x01d90000"> + <!-- @hide @SystemApi --> + <public name="config_optionalIpSecAlgorithms" /> + </staging-public-group> + + <staging-public-group type="drawable" first-id="0x01d80000"> + </staging-public-group> + + <staging-public-group type="layout" first-id="0x01d70000"> + </staging-public-group> + + <staging-public-group type="anim" first-id="0x01d60000"> + </staging-public-group> + + <staging-public-group type="animator" first-id="0x01d50000"> + </staging-public-group> + + <staging-public-group type="interpolator" first-id="0x01d40000"> + </staging-public-group> + + <staging-public-group type="mipmap" first-id="0x01d30000"> + </staging-public-group> + + <staging-public-group type="integer" first-id="0x01d20000"> + </staging-public-group> + + <staging-public-group type="transition" first-id="0x01d10000"> + </staging-public-group> + + <staging-public-group type="raw" first-id="0x01d00000"> + </staging-public-group> + + <staging-public-group type="bool" first-id="0x01cf0000"> + <!-- @hide @TestApi --> + <public name="config_preventImeStartupUnlessTextEditor" /> + <!-- @hide @SystemApi --> + <public name="config_enableQrCodeScannerOnLockScreen" /> + </staging-public-group> + + <staging-public-group type="fraction" first-id="0x01ce0000"> + </staging-public-group> + +</resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 05ebef9bf56f..f803c35f7f1d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1248,14 +1248,13 @@ Malicious apps may use this to erase or modify your call log.</string> <!-- Title of the body sensors permission, listed so the user can decide whether to allow the application to access body sensor data. [CHAR LIMIT=80] --> - <string name="permlab_bodySensors">access body sensors (like heart rate monitors) - </string> + <string name="permlab_bodySensors">Access body sensor data, like heart rate, while in use</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">Access to data from body sensors such as heart rate, temperature, blood oxygen percentage, etc.</string> + <string name="permdesc_bodySensors" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in use.</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> + <string name="permlab_bodySensors_background">Access body sensor data, like heart rate, 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> + <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is 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> @@ -5731,16 +5730,17 @@ <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] --> <string name="harmful_app_warning_title">Harmful app detected</string> - <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] --> - <string name="log_access_confirmation_title">System log access request</string> + <!-- Title for the log access confirmation dialog. [CHAR LIMIT=NONE] --> + <string name="log_access_confirmation_title">Allow <xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> to access all device logs?</string> <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] --> <string name="log_access_confirmation_allow">Only this time</string> <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] --> <string name="log_access_confirmation_deny">Don\u2019t allow</string> <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]--> - <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging. - These logs might contain information that apps and services on your device have written.</string> + <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. + \n\nIf you don’t allow this app to access all device logs, it can still access its own logs, and your device manufacturer may still be able to access some logs or info on your device. Learn more + </string> <!-- Privacy notice do not show [CHAR LIMIT=20] --> <string name="log_access_do_not_show_again">Don\u2019t show again</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 9553f95fe5c9..7a9f520b4d3c 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1499,5 +1499,43 @@ please see styles_device_defaults.xml. <item name="textColor">#555555</item> </style> + <!-- The style for log access consent text --> + <style name="AllowLogAccess"> + <item name="android:layout_width">332dp</item> + <item name="android:textSize">24sp</item> + <item name="android:textColor">@android:color/system_neutral1_900</item> + <item name="android:fontFamily">google-sans</item> + </style> + + <style name="PrimaryAllowLogAccess"> + <item name="android:layout_width">332dp</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/system_neutral1_900</item> + <item name="android:fontFamily">google-sans</item> + </style> + + <style name="PermissionGrantButtonTop" + parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"> + <item name="android:layout_width">332dp</item> + <item name="android:layout_height">56dp</item> + <item name="android:layout_marginTop">2dp</item> + <item name="android:layout_marginBottom">2dp</item> + <item name="android:fontFamily">google-sans-medium</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/system_neutral1_900</item> + <item name="android:background">@drawable/grant_permissions_buttons_top</item> + </style> + + <style name="PermissionGrantButtonBottom" + parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"> + <item name="android:layout_width">332dp</item> + <item name="android:layout_height">56dp</item> + <item name="android:layout_marginTop">2dp</item> + <item name="android:layout_marginBottom">2dp</item> + <item name="android:fontFamily">google-sans-medium</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/system_neutral1_900</item> + <item name="android:background">@drawable/grant_permissions_buttons_bottom</item> + </style> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2e1c6c2afc78..6f34b3f899f1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1325,6 +1325,7 @@ <java-symbol type="dimen" name="lock_pattern_dot_line_width" /> <java-symbol type="dimen" name="lock_pattern_dot_size" /> <java-symbol type="dimen" name="lock_pattern_dot_size_activated" /> + <java-symbol type="dimen" name="lock_pattern_dot_hit_factor" /> <java-symbol type="dimen" name="lock_pattern_fade_away_gradient_width" /> <java-symbol type="drawable" name="clock_dial" /> <java-symbol type="drawable" name="clock_hand_hour" /> @@ -3883,6 +3884,10 @@ <java-symbol type="string" name="log_access_confirmation_deny" /> <java-symbol type="string" name="log_access_confirmation_title" /> <java-symbol type="string" name="log_access_confirmation_body" /> + <java-symbol type="layout" name="log_access_user_consent_dialog_permission" /> + <java-symbol type="id" name="log_access_dialog_title" /> + <java-symbol type="id" name="log_access_dialog_allow_button" /> + <java-symbol type="id" name="log_access_dialog_deny_button" /> <java-symbol type="string" name="config_defaultAssistantAccessComponent" /> diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java new file mode 100644 index 000000000000..fa7d7214d289 --- /dev/null +++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Test; + +/** + * Test for verifying the behavior of {@link IpcDataCache}. This test does + * not use any actual binder calls - it is entirely self-contained. This test also relies + * on the test mode of {@link IpcDataCache} because Android SELinux rules do + * not grant test processes the permission to set system properties. + * <p> + * Build/Install/Run: + * atest FrameworksCoreTests:IpcDataCacheTest + */ +@SmallTest +public class IpcDataCacheTest { + + // Configuration for creating caches + private static final String MODULE = IpcDataCache.MODULE_TEST; + private static final String API = "testApi"; + + // This class is a proxy for binder calls. It contains a counter that increments + // every time the class is queried. + private static class ServerProxy { + // The number of times this class was queried. + private int mCount = 0; + + // A single query. The key behavior is that the query count is incremented. + boolean query(int x) { + mCount++; + return value(x); + } + + // Return the expected value of an input, without incrementing the query count. + boolean value(int x) { + return x % 3 == 0; + } + + // Verify the count. + void verify(int x) { + assertEquals(x, mCount); + } + } + + // The functions for querying the server. + private static class ServerQuery + extends IpcDataCache.QueryHandler<Integer, Boolean> { + private final ServerProxy mServer; + + ServerQuery(ServerProxy server) { + mServer = server; + } + + @Override + public Boolean apply(Integer x) { + return mServer.query(x); + } + @Override + public boolean shouldBypassCache(Integer x) { + return x % 13 == 0; + } + } + + // Clear the test mode after every test, in case this process is used for other + // tests. This also resets the test property map. + @After + public void tearDown() throws Exception { + IpcDataCache.setTestMode(false); + } + + // This test is disabled pending an sepolicy change that allows any app to set the + // test property. + @Test + public void testBasicCache() { + + // A stand-in for the binder. The test verifies that calls are passed through to + // this class properly. + ServerProxy tester = new ServerProxy(); + + // Create a cache that uses simple arithmetic to computer its values. + IpcDataCache<Integer, Boolean> testCache = + new IpcDataCache<>(4, MODULE, API, "testCache1", + new ServerQuery(tester)); + + IpcDataCache.setTestMode(true); + testCache.testPropertyName(); + + tester.verify(0); + assertEquals(tester.value(3), testCache.query(3)); + tester.verify(1); + assertEquals(tester.value(3), testCache.query(3)); + tester.verify(2); + testCache.invalidateCache(); + assertEquals(tester.value(3), testCache.query(3)); + tester.verify(3); + assertEquals(tester.value(5), testCache.query(5)); + tester.verify(4); + assertEquals(tester.value(5), testCache.query(5)); + tester.verify(4); + assertEquals(tester.value(3), testCache.query(3)); + tester.verify(4); + + // Invalidate the cache, and verify that the next read on 3 goes to the server. + testCache.invalidateCache(); + assertEquals(tester.value(3), testCache.query(3)); + tester.verify(5); + + // Test bypass. The query for 13 always bypasses the cache. + assertEquals(tester.value(12), testCache.query(12)); + assertEquals(tester.value(13), testCache.query(13)); + assertEquals(tester.value(14), testCache.query(14)); + tester.verify(8); + assertEquals(tester.value(12), testCache.query(12)); + assertEquals(tester.value(13), testCache.query(13)); + assertEquals(tester.value(14), testCache.query(14)); + tester.verify(9); + } + + @Test + public void testDisableCache() { + + // A stand-in for the binder. The test verifies that calls are passed through to + // this class properly. + ServerProxy tester = new ServerProxy(); + + // Three caches, all using the same system property but one uses a different name. + IpcDataCache<Integer, Boolean> cache1 = + new IpcDataCache<>(4, MODULE, API, "cacheA", + new ServerQuery(tester)); + IpcDataCache<Integer, Boolean> cache2 = + new IpcDataCache<>(4, MODULE, API, "cacheA", + new ServerQuery(tester)); + IpcDataCache<Integer, Boolean> cache3 = + new IpcDataCache<>(4, MODULE, API, "cacheB", + new ServerQuery(tester)); + + // Caches are enabled upon creation. + assertEquals(false, cache1.getDisabledState()); + assertEquals(false, cache2.getDisabledState()); + assertEquals(false, cache3.getDisabledState()); + + // Disable the cache1 instance. Only cache1 is disabled + cache1.disableInstance(); + assertEquals(true, cache1.getDisabledState()); + assertEquals(false, cache2.getDisabledState()); + assertEquals(false, cache3.getDisabledState()); + + // Disable cache1. This will disable cache1 and cache2 because they share the + // same name. cache3 has a different name and will not be disabled. + cache1.disableForCurrentProcess(); + assertEquals(true, cache1.getDisabledState()); + assertEquals(true, cache2.getDisabledState()); + assertEquals(false, cache3.getDisabledState()); + + // Create a new cache1. Verify that the new instance is disabled. + cache1 = new IpcDataCache<>(4, MODULE, API, "cacheA", + new ServerQuery(tester)); + assertEquals(true, cache1.getDisabledState()); + + // Remove the record of caches being locally disabled. This is a clean-up step. + cache1.forgetDisableLocal(); + assertEquals(true, cache1.getDisabledState()); + assertEquals(true, cache2.getDisabledState()); + assertEquals(false, cache3.getDisabledState()); + + // Create a new cache1. Verify that the new instance is not disabled. + cache1 = new IpcDataCache<>(4, MODULE, API, "cacheA", + new ServerQuery(tester)); + assertEquals(false, cache1.getDisabledState()); + } + + private static class TestQuery + extends IpcDataCache.QueryHandler<Integer, String> { + + private int mRecomputeCount = 0; + + @Override + public String apply(Integer qv) { + mRecomputeCount += 1; + return "foo" + qv.toString(); + } + + int getRecomputeCount() { + return mRecomputeCount; + } + } + + private static class TestCache extends IpcDataCache<Integer, String> { + private final TestQuery mQuery; + + TestCache() { + this(MODULE, API); + } + + TestCache(String module, String api) { + this(module, api, new TestQuery()); + } + + TestCache(String module, String api, TestQuery query) { + super(4, module, api, "testCache7", query); + mQuery = query; + setTestMode(true); + testPropertyName(); + } + + int getRecomputeCount() { + return mQuery.getRecomputeCount(); + } + } + + @Test + public void testCacheRecompute() { + TestCache cache = new TestCache(); + cache.invalidateCache(); + assertEquals(cache.isDisabled(), false); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo6", cache.query(6)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + // Invalidate the cache with a direct call to the property. + IpcDataCache.invalidateCache(MODULE, API); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(4, cache.getRecomputeCount()); + } + + @Test + public void testCacheInitialState() { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + + @Test + public void testCachePropertyUnset() { + final String UNSET_API = "otherApi"; + TestCache cache = new TestCache(MODULE, UNSET_API); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + } + + @Test + public void testCacheDisableState() { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + cache.disableSystemWide(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(5, cache.getRecomputeCount()); + cache.invalidateCache(); // Should not reenable + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(7, cache.getRecomputeCount()); + } + + @Test + public void testLocalProcessDisable() { + TestCache cache = new TestCache(); + assertEquals(cache.isDisabled(), false); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals(cache.isDisabled(), false); + cache.disableForCurrentProcess(); + assertEquals(cache.isDisabled(), true); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } +} diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java index 78a8f7b3f32e..c4c983d24af9 100644 --- a/core/tests/coretests/src/android/view/MotionEventTest.java +++ b/core/tests/coretests/src/android/view/MotionEventTest.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InputDevice.SOURCE_CLASS_POINTER; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.TOOL_TYPE_FINGER; @@ -214,4 +215,27 @@ public class MotionEventTest { rotInvalid.transform(mat); assertEquals(-1, rotInvalid.getSurfaceRotation()); } + + @Test + public void testUsesPointerSourceByDefault() { + final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, + ACTION_DOWN, 0 /* x */, 0 /* y */, 0 /* metaState */); + assertTrue(event.isFromSource(SOURCE_CLASS_POINTER)); + } + + @Test + public void testLocationOffsetOnlyAppliedToNonPointerSources() { + final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, + ACTION_DOWN, 10 /* x */, 20 /* y */, 0 /* metaState */); + event.offsetLocation(40, 50); + + // The offset should be applied since a pointer source is used by default. + assertEquals(50, (int) event.getX()); + assertEquals(70, (int) event.getY()); + + // The offset should not be applied if the source is changed to a non-pointer source. + event.setSource(InputDevice.SOURCE_JOYSTICK); + assertEquals(10, (int) event.getX()); + assertEquals(20, (int) event.getY()); + } } diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternViewTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternViewTest.java new file mode 100644 index 000000000000..8ba49663ad09 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternViewTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +import android.content.Context; + +import androidx.test.annotation.UiThreadTest; + +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toolbar; + + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.rule.UiThreadTestRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.android.internal.R; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +@RunWith(Parameterized.class) +@SmallTest +public class LockPatternViewTest { + + @Rule + public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); + + private final int mViewSize; + private final float mDefaultError; + private final float mDot1x; + private final float mDot1y; + private final float mDot2x; + private final float mDot2y; + private final float mDot3x; + private final float mDot3y; + private final float mDot5x; + private final float mDot5y; + private final float mDot7x; + private final float mDot7y; + private final float mDot9x; + private final float mDot9y; + + private Context mContext; + private LockPatternView mLockPatternView; + @Mock + private LockPatternView.OnPatternListener mPatternListener; + @Captor + private ArgumentCaptor<List<LockPatternView.Cell>> mCellsArgumentCaptor; + + public LockPatternViewTest(int viewSize) { + mViewSize = viewSize; + float cellSize = viewSize / 3f; + mDefaultError = cellSize * 0.2f; + mDot1x = cellSize / 2f; + mDot1y = cellSize / 2f; + mDot2x = cellSize + mDot1x; + mDot2y = mDot1y; + mDot3x = cellSize + mDot2x; + mDot3y = mDot1y; + // dot4 is skipped as redundant + mDot5x = cellSize + mDot1x; + mDot5y = cellSize + mDot1y; + // dot6 is skipped as redundant + mDot7x = mDot1x; + mDot7y = cellSize * 2 + mDot1y; + // dot8 is skipped as redundant + mDot9x = cellSize * 2 + mDot7x; + mDot9y = mDot7y; + } + + @Parameterized.Parameters + public static Collection primeNumbers() { + return Arrays.asList(192, 512, 768, 1024); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext = InstrumentationRegistry.getContext(); + mLockPatternView = new LockPatternView(mContext, null); + int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(mViewSize, + View.MeasureSpec.EXACTLY); + int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(mViewSize, + View.MeasureSpec.EXACTLY); + mLockPatternView.measure(widthMeasureSpec, heightMeasureSpec); + mLockPatternView.layout(0, 0, mLockPatternView.getMeasuredWidth(), + mLockPatternView.getMeasuredHeight()); + } + + @UiThreadTest + @Test + public void downStartsPattern() { + mLockPatternView.setOnPatternListener(mPatternListener); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, mDot1x, mDot1y, 1)); + verify(mPatternListener).onPatternStart(); + } + + @UiThreadTest + @Test + public void up_completesPattern() { + mLockPatternView.setOnPatternListener(mPatternListener); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, mDot1x, mDot1y, 1)); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, mDot1x, mDot1y, 1)); + verify(mPatternListener).onPatternDetected(any()); + } + + @UiThreadTest + @Test + public void moveToDot_hitsDot() { + mLockPatternView.setOnPatternListener(mPatternListener); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, mDot1x, mDot1y, 1)); + verify(mPatternListener).onPatternStart(); + } + + @UiThreadTest + @Test + public void moveOutside_doesNotHitsDot() { + mLockPatternView.setOnPatternListener(mPatternListener); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 2f, 2f, 1)); + verify(mPatternListener, never()).onPatternStart(); + } + + @UiThreadTest + @Test + public void moveAlongTwoDots_hitsTwo() { + mLockPatternView.setOnPatternListener(mPatternListener); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); + makeMove(mDot1x, mDot1y, mDot2x, mDot2y, 6); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, mDot2x, mDot2y, 1)); + + verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture()); + List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue(); + assertThat(patternCells, hasSize(2)); + assertThat(patternCells, + contains(LockPatternView.Cell.of(0, 0), LockPatternView.Cell.of(0, 1))); + } + + @UiThreadTest + @Test + public void moveAlongTwoDotsDiagonally_hitsTwo() { + mLockPatternView.setOnPatternListener(mPatternListener); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); + makeMove(mDot1x, mDot1y, mDot5x, mDot5y, 6); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, mDot5x, mDot5y, 1)); + + verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture()); + List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue(); + assertThat(patternCells, hasSize(2)); + assertThat(patternCells, + contains(LockPatternView.Cell.of(0, 0), LockPatternView.Cell.of(1, 1))); + } + + @UiThreadTest + @Test + public void moveAlongZPattern_hitsDots() { + mLockPatternView.setOnPatternListener(mPatternListener); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); + makeMove(mDot1x, mDot1y, mDot3x + mDefaultError, mDot3y, 10); + makeMove(mDot3x - mDefaultError, mDot3y, mDot7x, mDot7y, 10); + makeMove(mDot7x, mDot7y - mDefaultError, mDot9x, mDot9y - mDefaultError, 10); + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, mViewSize - mDefaultError, + mViewSize - mDefaultError, 1)); + + verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture()); + List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue(); + assertThat(patternCells, hasSize(7)); + assertThat(patternCells, + contains(LockPatternView.Cell.of(0, 0), + LockPatternView.Cell.of(0, 1), + LockPatternView.Cell.of(0, 2), + LockPatternView.Cell.of(1, 1), + LockPatternView.Cell.of(2, 0), + LockPatternView.Cell.of(2, 1), + LockPatternView.Cell.of(2, 2))); + } + + private void makeMove(float xFrom, float yFrom, float xTo, float yTo, int numberOfSteps) { + for (int i = 0; i < numberOfSteps; i++) { + float progress = i / (numberOfSteps - 1f); + float rest = 1f - progress; + mLockPatternView.onTouchEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, + /* x= */ xFrom * rest + xTo * progress, + /* y= */ yFrom * rest + yTo * progress, + 1)); + } + } +} diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index 2ff888b06dd8..6abe34b1d675 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -19,12 +19,228 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; +import android.view.Window; import libcore.util.NativeAllocationRegistry; /** - * Shader that calculates per-pixel color via a user defined Android Graphics Shading Language - * (AGSL) function. + * <p>A {@link RuntimeShader} calculates a per-pixel color based on the output of a user defined + * Android Graphics Shading Language (AGSL) function.</p> + * + * <h3>Android Graphics Shading Language</h3> + * <p>The AGSL syntax is very similar to OpenGL ES Shading Language, but there are some important + * differences that are highlighted here. Most of these differences are summed up in one basic fact: + * <b>With GPU shading languages, you are programming a stage of the GPU pipeline. With AGSL, you + * are programming a stage of the {@link Canvas} or {@link RenderNode} drawing pipeline.</b></p> + * + * <p>In particular, a GLSL fragment shader controls the entire behavior of the GPU between the + * rasterizer and the blending hardware. That shader does all of the work to compute a color, and + * the color it generates is exactly what is fed to the blending stage of the pipeline.</p> + * + * <p>In contrast, AGSL functions exist as part of a larger pipeline. When you issue a + * {@link Canvas} drawing operation, Android (generally) assembles a single GPU fragment shader to + * do all of the required work. This shader typically includes several pieces. For example, it might + * include:</p> + * <ul> + * <li>Evaluating whether a pixel falls inside or outside of the shape being drawn (or on the + * border, where it might apply antialiasing).</li> + * <li>Evaluating whether a pixel falls inside or outside of the clipping region (again, with + * possible antialiasing logic for border pixels).</li> + * <li>Logic for the {@link Shader}, {@link ColorFilter}, and {@link BlendMode} on the + * {@link Paint}.</li> + * <li>Color space conversion code, as part of Android’s color management.</li> + * </ul> + * + * <p>A {@link RuntimeShader}, like other {@link Shader} types, effectively contributes a function + * to the GPU’s fragment shader.</p> + * + * <h3>AGSL Shader Execution</h3> + * <p>Just like a GLSL shader, an AGSL shader begins execution in a main function. Unlike GLSL, the + * function receives as an input parameter the position of the pixel within the {@link Canvas} or + * {@link RenderNode} coordinate space (similar to gl_fragCoord) and returns the color to be shaded + * as a vec4 (similar to out vec4 color or gl_FragColor in GLSL).</p> + * + * <pre class="prettyprint"> + * vec4 main(vec2 canvas_coordinates); + * </pre> + * + * <p>AGSL and GLSL use different coordinate spaces by default. In GLSL, the fragment coordinate + * (fragCoord) is relative to the lower left. AGSL matches the screen coordinate system of the + * Android {@link Canvas} which has its origin as the upper left corner. This means that the + * coordinates provided as a parameter in the main function are local to the canvas with the + * exception of any {@link Shader#getLocalMatrix(Matrix)} transformations applied to this shader. + * Additionally, if the shader is invoked by another using {@link #setInputShader(String, Shader)}, + * then that parent shader may modify the input coordinates arbitrarily.</p> + * + * <h3>AGSL and Color Spaces</h3> + * <p>Android Graphics and by extension {@link RuntimeShader} are color managed. The working + * {@link ColorSpace} for an AGSL shader is defined to be the color space of the destination, which + * in most cases is determined by {@link Window#setColorMode(int)}.</p> + * + * <p>When authoring an AGSL shader, you won’t know what the working color space is. For many + * effects, this is fine because by default color inputs are automatically converted into the + * working color space. For certain effects, it may be important to do some math in a fixed, known + * color space. A common example is lighting – to get physically accurate lighting, math should be + * done in a linear color space. To help with this, AGSL provides two intrinsic functions that + * convert colors between the working color space and the + * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB} color space: + * + * <pre class="prettyprint"> + * vec3 toLinearSrgb(vec3 color); + * vec3 fromLinearSrgb(vec3 color);</pre> + * + * <h3>AGSL and Premultiplied Alpha</h3> + * <p>When dealing with transparent colors, there are two (common) possible representations: + * straight (unassociated) alpha and premultiplied (associated) alpha. In ASGL the color returned + * by the main function is expected to be premultiplied. AGSL’s use of premultiplied alpha + * implies: + * </p> + * + * <ul> + * <li>If your AGSL shader will return transparent colors, be sure to multiply the RGB by A. The + * resulting color should be [R*A, G*A, B*A, A], not [R, G, B, A].</li> + * <li>For more complex shaders, you must understand which of your colors are premultiplied vs. + * straight. Many operations don’t make sense if you mix both kinds of color together.</li> + * </ul> + * + * <h3>Uniforms</h3> + * <p>AGSL, like GLSL, exposes the concept of uniforms. An AGSL uniform is defined as a read-only, + * global variable that is accessible by the AGSL code and is initialized by a number of setter + * methods on {@link RuntimeShader}. AGSL exposes two primitive uniform data types (float, int) and + * two specialized types (colors, shaders) that are outlined below.</p> + * + * <h4>Primitive Uniforms</h4> + * <p>There are two primitive uniform types supported by AGSL, float and int. For these types and + * uniforms representing a grouping of these types, like arrays and matrices, there are + * corresponding {@link RuntimeShader} methods to initialize them. + * <table border="2" width="85%" align="center" cellpadding="5"> + * <thead> + * <tr><th>Java Type</th> <th>AGSL Type</th> <th>Method</th> </tr> + * </thead> + * + * <tbody> + * <tr> + * <td rowspan="4">Floats</td> + * <td>float</td> + * <td>{@link RuntimeShader#setFloatUniform(String, float)}</td> + * </tr> + * <tr> + * <td>vec2</td> + * <td>{@link RuntimeShader#setFloatUniform(String, float, float)}</td> + * </tr> + * <tr> + * <td>vec3</td> + * <td>{@link RuntimeShader#setFloatUniform(String, float, float, float)}</td> + * </tr> + * <tr> + * <td>vec4</td> + * <td>{@link RuntimeShader#setFloatUniform(String, float, float, float, float)}</td> + * </tr> + * <tr> + * <td rowspan="4">Integers</td> + * <td>int</td> + * <td>{@link RuntimeShader#setIntUniform(String, int)}</td> + * </tr> + * <tr> + * <td>ivec2</td> + * <td>{@link RuntimeShader#setIntUniform(String, int, int)}</td> + * </tr> + * <tr> + * <td>ivec3</td> + * <td>{@link RuntimeShader#setIntUniform(String, int, int, int)}</td> + * </tr> + * <tr> + * <td>ivec4</td> + * <td>{@link RuntimeShader#setIntUniform(String, int, int, int, int)}</td> + * </tr> + * <tr> + * <td rowspan="2">Matrices and Arrays</td> + * <td>mat2, mat3, and mat4, and float[]</td> + * <td>{@link RuntimeShader#setFloatUniform(String, float[])}</td> + * </tr> + * <tr> + * <td>int[]</td> + * <td>{@link RuntimeShader#setIntUniform(String, int[])}</td> + * </tr> + * </tbody> + * </table> + * + * For example, a simple AGSL shader making use of a float uniform to modulate the transparency + * of the output color would look like:</p> + * + * <pre class="prettyprint"> + * uniform float alpha; + * vec4 main(vec2 canvas_coordinates) { + * vec3 red = vec3(1.0, 0.0, 0.0); + * return vec4(red * alpha, alpha); + * }</pre> + * + * <p>After creating a {@link RuntimeShader} with that program the uniform can then be initialized + * and updated per frame by calling {@link RuntimeShader#setFloatUniform(String, float)} with the + * value of alpha. The value of a primitive uniform defaults to 0 if it is declared in the AGSL + * shader but not initialized.</p> + * + * <h4>Color Uniforms</h4> + * <p>AGSL doesn't know if uniform variables contain colors, it won't automatically convert them to + * the working colorspace of the shader at runtime. However, you can label your vec4 uniform with + * the "layout(color)" qualifier which lets Android know that the uniform will be used as a color. + * Doing so allows AGSL to transform the uniform value to the working color space. In AGSL, declare + * the uniform like this: + * + * <pre class="prettyprint"> + * layout(color) uniform vec4 inputColorA; + * layout(color) uniform vec4 inputColorB; + * vec4 main(vec2 canvas_coordinates) { + * // blend the two colors together and return the resulting color + * return mix(inputColorA, inputColorB, 0.5); + * }</pre> + * + * <p>After creating a {@link RuntimeShader} with that program the uniforms can + * then be initialized and updated per frame by calling + * {@link RuntimeShader#setColorUniform(String, int)}, + * {@link RuntimeShader#setColorUniform(String, long)}, or + * {@link RuntimeShader#setColorUniform(String, Color)} with the desired colors. The value of a + * color uniform is undefined if it is declared in the AGSL shader but not initialized.</p> + * + * <h4>Shader Uniforms</h4> + * In GLSL, a fragment shader can sample a texture. For AGSL instead of sampling textures you can + * sample from any {@link Shader}, which includes but is not limited to {@link BitmapShader}. To + * make it clear that you are operating on an {@link Shader} object there is no "sample" + * method. Instead, the shader uniform has an "eval()" method. This distinction enables AGSL shaders + * to sample from existing bitmap and gradient shaders as well as other {@link RuntimeShader} + * objects. In AGSL, declare the uniform like this: + * + * <pre class="prettyprint"> + * uniform shader myShader; + * vec4 main(vec2 canvas_coordinates) { + * // swap the red and blue color channels when sampling from myShader + * return myShader.sample(canvas_coordinates).bgra; + * }</pre> + * + * <p>After creating a {@link RuntimeShader} with that program the shader uniform can + * then be initialized and updated per frame by calling + * {@link RuntimeShader#setInputShader(String, Shader)} with the desired shader. The value of a + * shader uniform is undefined if it is declared in the AGSL shader but not initialized.</p> + * + * <p>Although most {@link BitmapShader}s contain colors that should be color managed, some contain + * data that isn’t actually colors. This includes bitmaps storing normals, material properties + * (e.g. roughness), heightmaps, or any other purely mathematical data that happens to be stored in + * a bitmap. When using these kinds of shaders in AGSL, you probably want to initialize them with + * {@link #setInputBuffer(String, BitmapShader)}. Shaders initialized this way work much like + * a regular {@link BitmapShader} (including filtering and tiling), with a few major differences: + * <ul> + * <li>No color space transformation is applied (the color space of the bitmap is ignored).</li> + * <li>Bitmaps that return false for {@link Bitmap#isPremultiplied()} are not automatically + * premultiplied.</li> + * </ul> + * + * <p>In addition, when sampling from a {@link BitmapShader} be aware that the shader does not use + * normalized coordinates (like a texture in GLSL). It uses (0, 0) in the upper-left corner, and + * (width, height) in the bottom-right corner. Normally, this is exactly what you want. If you’re + * evaluating the shader with coordinates based on the ones passed to your AGSL program, the scale + * is correct. However, if you want to adjust those coordinates (to do some kind of re-mapping of + * the bitmap), remember that the coordinates are local to the canvas.</p> + * */ public class RuntimeShader extends Shader { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 31dd10a8ed53..e7961c94928c 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -108,6 +108,16 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } + /** + * XDH represents Curve 25519 providers. + */ + public static class XDH extends AndroidKeyStoreKeyPairGeneratorSpi { + // XDH is treated as EC. + public XDH() { + super(KeymasterDefs.KM_ALGORITHM_EC); + } + } + /* * These must be kept in sync with system/security/keystore/defaults.h */ @@ -242,6 +252,23 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } catch (NullPointerException | IllegalArgumentException e) { throw new InvalidAlgorithmParameterException(e); } + } else if (params instanceof NamedParameterSpec) { + NamedParameterSpec namedSpec = (NamedParameterSpec) params; + // Android Keystore cannot support initialization from a NamedParameterSpec + // because an alias for the key is needed (a KeyGenParameterSpec cannot be + // constructed). + if (namedSpec.getName().equalsIgnoreCase(NamedParameterSpec.X25519.getName()) + || namedSpec.getName().equalsIgnoreCase( + NamedParameterSpec.ED25519.getName())) { + throw new IllegalArgumentException( + "This KeyPairGenerator cannot be initialized using NamedParameterSpec." + + " use " + KeyGenParameterSpec.class.getName() + " or " + + KeyPairGeneratorSpec.class.getName()); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm specified via NamedParameterSpec: " + + namedSpec.getName()); + } } else { throw new InvalidAlgorithmParameterException( "Unsupported params class: " + params.getClass().getName() diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index 358104fffbf6..d31499e8b36d 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -83,16 +83,12 @@ public class AndroidKeyStoreProvider extends Provider { // java.security.KeyPairGenerator put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC"); put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); - put("KeyPairGenerator." + X25519_ALIAS, - PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); - put("KeyPairGenerator." + ED25519_OID, - PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); + put("KeyPairGenerator.XDH", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$XDH"); // java.security.KeyFactory putKeyFactoryImpl("EC"); putKeyFactoryImpl("RSA"); - putKeyFactoryImpl(X25519_ALIAS); - putKeyFactoryImpl(ED25519_OID); + putKeyFactoryImpl("XDH"); // javax.crypto.KeyGenerator put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES"); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java index eb9429747b66..1c49881904e4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java @@ -18,17 +18,73 @@ package androidx.window.common; import static androidx.window.util.ExtensionHelper.isZero; +import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; +import android.util.Log; import androidx.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ -final class CommonDisplayFeature implements DisplayFeature { +/** A representation of a folding feature for both Extension and Sidecar. + * For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and + * {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of + * {@link androidx.window.extensions.layout.FoldingFeature}. + */ +public final class CommonFoldingFeature { + + private static final boolean DEBUG = false; + + public static final String TAG = CommonFoldingFeature.class.getSimpleName(); + + /** + * A common type to represent a hinge where the screen is continuous. + */ + public static final int COMMON_TYPE_FOLD = 1; + + /** + * A common type to represent a hinge where there is a physical gap separating multiple + * displays. + */ + public static final int COMMON_TYPE_HINGE = 2; + + @IntDef({COMMON_TYPE_FOLD, COMMON_TYPE_HINGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface Type { + } + + /** + * A common state to represent when the state is not known. One example is if the device is + * closed. We do not emit this value for developers but is useful for implementation reasons. + */ + public static final int COMMON_STATE_UNKNOWN = -1; + + /** + * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar + * and Extensions do not match exactly. + */ + public static final int COMMON_STATE_FLAT = 3; + /** + * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in + * Sidecar and Extensions do not match exactly. + */ + public static final int COMMON_STATE_HALF_OPENED = 2; + + /** + * The possible states for a folding hinge. + */ + @IntDef({COMMON_STATE_UNKNOWN, COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED}) + @Retention(RetentionPolicy.SOURCE) + public @interface State { + } + private static final Pattern FEATURE_PATTERN = Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]-?(flat|half-opened)?"); @@ -38,17 +94,49 @@ final class CommonDisplayFeature implements DisplayFeature { private static final String PATTERN_STATE_FLAT = "flat"; private static final String PATTERN_STATE_HALF_OPENED = "half-opened"; - // TODO(b/183049815): Support feature strings that include the state of the feature. + /** + * Parse a {@link List} of {@link CommonFoldingFeature} from a {@link String}. + * @param value a {@link String} representation of multiple {@link CommonFoldingFeature} + * separated by a ":". + * @param hingeState a global fallback value for a {@link CommonFoldingFeature} if one is not + * specified in the input. + * @throws IllegalArgumentException if the provided string is improperly formatted or could not + * otherwise be parsed. + * @see #FEATURE_PATTERN + * @return {@link List} of {@link CommonFoldingFeature}. + */ + static List<CommonFoldingFeature> parseListFromString(@NonNull String value, + @State int hingeState) { + List<CommonFoldingFeature> features = new ArrayList<>(); + String[] featureStrings = value.split(";"); + for (String featureString : featureStrings) { + CommonFoldingFeature feature; + try { + feature = CommonFoldingFeature.parseFromString(featureString, hingeState); + } catch (IllegalArgumentException e) { + if (DEBUG) { + Log.w(TAG, "Failed to parse display feature: " + featureString, e); + } + continue; + } + features.add(feature); + } + return features; + } /** * Parses a display feature from a string. * + * @param string A {@link String} representation of a {@link CommonFoldingFeature}. + * @param hingeState A fallback value for the {@link State} if it is not specified in the input. * @throws IllegalArgumentException if the provided string is improperly formatted or could not * otherwise be parsed. + * @return {@link CommonFoldingFeature} represented by the {@link String} value. * @see #FEATURE_PATTERN */ @NonNull - static CommonDisplayFeature parseFromString(@NonNull String string) { + private static CommonFoldingFeature parseFromString(@NonNull String string, + @State int hingeState) { Matcher featureMatcher = FEATURE_PATTERN.matcher(string); if (!featureMatcher.matches()) { throw new IllegalArgumentException("Malformed feature description format: " + string); @@ -59,10 +147,10 @@ final class CommonDisplayFeature implements DisplayFeature { int type; switch (featureType) { case FEATURE_TYPE_FOLD: - type = 1 /* TYPE_FOLD */; + type = COMMON_TYPE_FOLD; break; case FEATURE_TYPE_HINGE: - type = 2 /* TYPE_HINGE */; + type = COMMON_TYPE_HINGE; break; default: { throw new IllegalArgumentException("Malformed feature type: " + featureType); @@ -79,7 +167,7 @@ final class CommonDisplayFeature implements DisplayFeature { } String stateString = featureMatcher.group(6); stateString = stateString == null ? "" : stateString; - Integer state; + final int state; switch (stateString) { case PATTERN_STATE_FLAT: state = COMMON_STATE_FLAT; @@ -88,10 +176,10 @@ final class CommonDisplayFeature implements DisplayFeature { state = COMMON_STATE_HALF_OPENED; break; default: - state = null; + state = hingeState; break; } - return new CommonDisplayFeature(type, state, featureRect); + return new CommonFoldingFeature(type, state, featureRect); } catch (NumberFormatException e) { throw new IllegalArgumentException("Malformed feature description: " + string, e); } @@ -99,11 +187,11 @@ final class CommonDisplayFeature implements DisplayFeature { private final int mType; @Nullable - private final Integer mState; + private final int mState; @NonNull private final Rect mRect; - CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) { + CommonFoldingFeature(int type, int state, @NonNull Rect rect) { assertValidState(state); this.mType = type; this.mState = state; @@ -114,16 +202,19 @@ final class CommonDisplayFeature implements DisplayFeature { this.mRect = rect; } + /** Returns the type of the feature. */ + @Type public int getType() { return mType; } - /** Returns the state of the feature, or {@code null} if the feature has no state. */ - @Nullable - public Integer getState() { + /** Returns the state of the feature.*/ + @State + public int getState() { return mState; } + /** Returns the bounds of the feature. */ @NonNull public Rect getRect() { return mRect; @@ -133,7 +224,7 @@ final class CommonDisplayFeature implements DisplayFeature { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - CommonDisplayFeature that = (CommonDisplayFeature) o; + CommonFoldingFeature that = (CommonFoldingFeature) o; return mType == that.mType && Objects.equals(mState, that.mState) && mRect.equals(that.mRect); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java index fa9a5a8b7a1b..6987401525b4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerPostureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java @@ -18,11 +18,15 @@ package androidx.window.common; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; +import static androidx.window.common.CommonFoldingFeature.parseListFromString; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; +import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; @@ -30,6 +34,7 @@ import androidx.window.util.BaseDataProducer; import com.android.internal.R; +import java.util.List; import java.util.Optional; /** @@ -37,10 +42,13 @@ import java.util.Optional; * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources * config at {@link R.array#config_device_state_postures}. */ -public final class DeviceStateManagerPostureProducer extends BaseDataProducer<Integer> { - private static final String TAG = "ConfigDevicePostureProducer"; +public final class DeviceStateManagerFoldingFeatureProducer extends + BaseDataProducer<List<CommonFoldingFeature>> { + private static final String TAG = + DeviceStateManagerFoldingFeatureProducer.class.getSimpleName(); private static final boolean DEBUG = false; + private final Context mContext; private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); private int mCurrentDeviceState = INVALID_DEVICE_STATE; @@ -50,7 +58,8 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In notifyDataChanged(); }; - public DeviceStateManagerPostureProducer(@NonNull Context context) { + public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context) { + mContext = context; String[] deviceStatePosturePairs = context.getResources() .getStringArray(R.array.config_device_state_postures); for (String deviceStatePosturePair : deviceStatePosturePairs) { @@ -86,8 +95,17 @@ public final class DeviceStateManagerPostureProducer extends BaseDataProducer<In @Override @Nullable - public Optional<Integer> getData() { - final int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, -1); - return posture != -1 ? Optional.of(posture) : Optional.empty(); + public Optional<List<CommonFoldingFeature>> getData() { + final int globalHingeState = globalHingeState(); + String displayFeaturesString = mContext.getResources().getString( + R.string.config_display_features); + if (TextUtils.isEmpty(displayFeaturesString)) { + return Optional.empty(); + } + return Optional.of(parseListFromString(displayFeaturesString, globalHingeState)); + } + + private int globalHingeState() { + return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java deleted file mode 100644 index 573641857b99..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java +++ /dev/null @@ -1,60 +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 androidx.window.common; - -import android.annotation.IntDef; -import android.annotation.Nullable; -import android.graphics.Rect; - -import androidx.annotation.NonNull; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ -public interface DisplayFeature { - /** Returns the type of the feature. */ - int getType(); - - /** Returns the state of the feature, or {@code null} if the feature has no state. */ - @Nullable - @State - Integer getState(); - - /** Returns the bounds of the feature. */ - @NonNull - Rect getRect(); - - /** - * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar - * and Extensions do not match exactly. - */ - int COMMON_STATE_FLAT = 3; - /** - * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in - * Sidecar and Extensions do not match exactly. - */ - int COMMON_STATE_HALF_OPENED = 2; - - /** - * The possible states for a folding hinge. - */ - @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED}) - @Retention(RetentionPolicy.SOURCE) - @interface State {} - -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java new file mode 100644 index 000000000000..d923a46c3b5d --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.common; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; + +/** + * An empty implementation of {@link Application.ActivityLifecycleCallbacks} derived classes can + * implement the methods necessary. + */ +public class EmptyLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + } + + @Override + public void onActivityStarted(Activity activity) { + } + + @Override + public void onActivityResumed(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + } + + @Override + public void onActivityStopped(Activity activity) { + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java deleted file mode 100644 index cd2cadc082e1..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java +++ /dev/null @@ -1,74 +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 androidx.window.common; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; - -import androidx.window.util.BaseDataProducer; - -import com.android.internal.R; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * Implementation of {@link androidx.window.util.DataProducer} that produces - * {@link CommonDisplayFeature} parsed from a string stored in the resources config at - * {@link R.string#config_display_features}. - */ -public final class ResourceConfigDisplayFeatureProducer extends - BaseDataProducer<List<DisplayFeature>> { - private static final boolean DEBUG = false; - private static final String TAG = "ResourceConfigDisplayFeatureProducer"; - - private final Context mContext; - - public ResourceConfigDisplayFeatureProducer(@NonNull Context context) { - mContext = context; - } - - @Override - @Nullable - public Optional<List<DisplayFeature>> getData() { - String displayFeaturesString = mContext.getResources().getString( - R.string.config_display_features); - if (TextUtils.isEmpty(displayFeaturesString)) { - return Optional.empty(); - } - - List<DisplayFeature> features = new ArrayList<>(); - String[] featureStrings = displayFeaturesString.split(";"); - for (String featureString : featureStrings) { - CommonDisplayFeature feature; - try { - feature = CommonDisplayFeature.parseFromString(featureString); - } catch (IllegalArgumentException e) { - if (DEBUG) { - Log.w(TAG, "Failed to parse display feature: " + featureString, e); - } - continue; - } - features.add(feature); - } - return Optional.of(features); - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java deleted file mode 100644 index 2026df3fa979..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java +++ /dev/null @@ -1,96 +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 androidx.window.common; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; - -import androidx.window.util.BaseDataProducer; - -import java.util.Optional; - -/** - * Implementation of {@link androidx.window.util.DataProducer} that provides the device posture - * as an {@link Integer} from a value stored in {@link Settings}. - */ -public final class SettingsDevicePostureProducer extends BaseDataProducer<Integer> { - private static final String DEVICE_POSTURE = "device_posture"; - - private final Uri mDevicePostureUri = - Settings.Global.getUriFor(DEVICE_POSTURE); - - private final ContentResolver mResolver; - private final ContentObserver mObserver; - private boolean mRegisteredObservers; - - public SettingsDevicePostureProducer(@NonNull Context context) { - mResolver = context.getContentResolver(); - mObserver = new SettingsObserver(); - } - - @Override - @Nullable - public Optional<Integer> getData() { - int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, -1); - return posture == -1 ? Optional.empty() : Optional.of(posture); - } - - /** - * Registers settings observers, if needed. When settings observers are registered for this - * producer callbacks for changes in data will be triggered. - */ - public void registerObserversIfNeeded() { - if (mRegisteredObservers) { - return; - } - mRegisteredObservers = true; - mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */, - mObserver /* ContentObserver */); - } - - /** - * Unregisters settings observers, if needed. When settings observers are unregistered for this - * producer callbacks for changes in data will not be triggered. - */ - public void unregisterObserversIfNeeded() { - if (!mRegisteredObservers) { - return; - } - mRegisteredObservers = false; - mResolver.unregisterContentObserver(mObserver); - } - - private final class SettingsObserver extends ContentObserver { - SettingsObserver() { - super(new Handler(Looper.getMainLooper())); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (mDevicePostureUri.equals(uri)) { - notifyDataChanged(); - } - } - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java index 040662657a74..e9d213e06fa9 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java @@ -16,8 +16,10 @@ package androidx.window.common; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; +import static androidx.window.common.CommonFoldingFeature.parseListFromString; + import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -26,22 +28,19 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.text.TextUtils; -import android.util.Log; import androidx.window.util.BaseDataProducer; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; /** * Implementation of {@link androidx.window.util.DataProducer} that produces - * {@link CommonDisplayFeature} parsed from a string stored in {@link Settings}. + * {@link CommonFoldingFeature} parsed from a string stored in {@link Settings}. */ public final class SettingsDisplayFeatureProducer - extends BaseDataProducer<List<DisplayFeature>> { - private static final boolean DEBUG = false; - private static final String TAG = "SettingsDisplayFeatureProducer"; + extends BaseDataProducer<List<CommonFoldingFeature>> { private static final String DISPLAY_FEATURES = "display_features"; private final Uri mDisplayFeaturesUri = @@ -57,32 +56,17 @@ public final class SettingsDisplayFeatureProducer } @Override - @Nullable - public Optional<List<DisplayFeature>> getData() { + @NonNull + public Optional<List<CommonFoldingFeature>> getData() { String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); if (displayFeaturesString == null) { return Optional.empty(); } - List<DisplayFeature> features = new ArrayList<>(); if (TextUtils.isEmpty(displayFeaturesString)) { - return Optional.of(features); - } - String[] featureStrings = displayFeaturesString.split(";"); - - for (String featureString : featureStrings) { - CommonDisplayFeature feature; - try { - feature = CommonDisplayFeature.parseFromString(featureString); - } catch (IllegalArgumentException e) { - if (DEBUG) { - Log.w(TAG, "Failed to parse display feature: " + featureString, e); - } - continue; - } - features.add(feature); + return Optional.of(Collections.emptyList()); } - return Optional.of(features); + return Optional.of(parseListFromString(displayFeaturesString, COMMON_STATE_UNKNOWN)); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 6d8383372461..1d2b9384f47d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -28,7 +28,6 @@ import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityOptions; import android.app.ActivityThread; -import android.app.Application.ActivityLifecycleCallbacks; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; @@ -41,6 +40,8 @@ import android.os.Looper; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; +import androidx.window.common.EmptyLifecycleCallbacksAdapter; + import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -763,11 +764,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return shouldRetainAssociatedContainer(finishingContainer, associatedContainer); } - private final class LifecycleCallbacks implements ActivityLifecycleCallbacks { - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - } + private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter { @Override public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) { @@ -779,30 +776,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onActivityStarted(Activity activity) { - } - - @Override - public void onActivityResumed(Activity activity) { - } - - @Override - public void onActivityPaused(Activity activity) { - } - - @Override - public void onActivityStopped(Activity activity) { - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - } - - @Override - public void onActivityDestroyed(Activity activity) { - } - - @Override public void onActivityConfigurationChanged(Activity activity) { SplitController.this.onActivityConfigurationChanged(activity); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index fe9ce971d4d9..a4fbdbc493f5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -18,28 +18,30 @@ package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; -import static androidx.window.common.DisplayFeature.COMMON_STATE_FLAT; -import static androidx.window.common.DisplayFeature.COMMON_STATE_HALF_OPENED; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; +import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.annotation.Nullable; import android.app.Activity; +import android.app.Application; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.util.ArrayMap; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.common.DeviceStateManagerPostureProducer; -import androidx.window.common.DisplayFeature; -import androidx.window.common.ResourceConfigDisplayFeatureProducer; -import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.CommonFoldingFeature; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; +import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.SettingsDisplayFeatureProducer; import androidx.window.util.DataProducer; import androidx.window.util.PriorityDataProducer; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -56,36 +58,27 @@ import java.util.function.Consumer; */ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private static final String TAG = "SampleExtension"; - private static WindowLayoutComponent sInstance; private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = - new HashMap<>(); - - private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; - private final DataProducer<Integer> mDevicePostureProducer; + new ArrayMap<>(); private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; - private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; + private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; public WindowLayoutComponentImpl(Context context) { - mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); - mDevicePostureProducer = new PriorityDataProducer<>(List.of( - mSettingsDevicePostureProducer, - new DeviceStateManagerPostureProducer(context) - )); - + ((Application) context.getApplicationContext()) + .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); - mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( + mFoldingFeatureProducer = new PriorityDataProducer<>(List.of( mSettingsDisplayFeatureProducer, - new ResourceConfigDisplayFeatureProducer(context) + new DeviceStateManagerFoldingFeatureProducer(context) )); - - mDevicePostureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); - mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); + mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } /** * Adds a listener interested in receiving updates to {@link WindowLayoutInfo} + * * @param activity hosting a {@link android.view.Window} * @param consumer interested in receiving updates to {@link WindowLayoutInfo} */ @@ -97,6 +90,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** * Removes a listener no longer interested in receiving updates. + * * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo} */ public void removeWindowLayoutInfoListener( @@ -118,43 +112,34 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return mWindowLayoutChangeListeners.keySet(); } - protected boolean hasListeners() { - return !mWindowLayoutChangeListeners.isEmpty(); + @NonNull + private boolean isListeningForLayoutChanges(IBinder token) { + for (Activity activity: getActivitiesListeningForLayoutChanges()) { + if (token.equals(activity.getWindow().getAttributes().token)) { + return true; + } + } + return false; } - /** - * Calculate the {@link DisplayFeature.State} from the feature or the device posture producer. - * If the given {@link DisplayFeature.State} is not valid then {@code null} will be returned. - * The {@link FoldingFeature} should be ignored in the case of an invalid - * {@link DisplayFeature.State}. - * - * @param feature a {@link DisplayFeature} to provide the feature state if present. - * @return {@link DisplayFeature.State} of the hinge if present or the state from the posture - * produce if present. - */ - @Nullable - private Integer getFeatureState(DisplayFeature feature) { - Integer featureState = feature.getState(); - Optional<Integer> posture = mDevicePostureProducer.getData(); - Integer state = featureState == null ? posture.orElse(null) : featureState; - return convertToExtensionState(state); + protected boolean hasListeners() { + return !mWindowLayoutChangeListeners.isEmpty(); } /** * A convenience method to translate from the common feature state to the extensions feature - * state. More specifically, translates from {@link DisplayFeature.State} to + * state. More specifically, translates from {@link CommonFoldingFeature.State} to * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not * possible to translate, then we will return a {@code null} value. * - * @param state if it matches a value in {@link DisplayFeature.State}, {@code null} otherwise. - * @return a {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED} if - * the given state matches a value in {@link DisplayFeature.State} and {@code null} otherwise. + * @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null} + * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or + * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in + * {@link CommonFoldingFeature.State} and {@code null} otherwise. */ @Nullable - private Integer convertToExtensionState(@Nullable Integer state) { - if (state == null) { // The null check avoids a NullPointerException. - return null; - } else if (state == COMMON_STATE_FLAT) { + private Integer convertToExtensionState(int state) { + if (state == COMMON_STATE_FLAT) { return FoldingFeature.STATE_FLAT; } else if (state == COMMON_STATE_HALF_OPENED) { return FoldingFeature.STATE_HALF_OPENED; @@ -172,33 +157,30 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @NonNull private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { - List<androidx.window.extensions.layout.DisplayFeature> displayFeatures = - getDisplayFeatures(activity); + List<DisplayFeature> displayFeatures = getDisplayFeatures(activity); return new WindowLayoutInfo(displayFeatures); } /** - * Translate from the {@link DisplayFeature} to - * {@link androidx.window.extensions.layout.DisplayFeature} for a given {@link Activity}. If a - * {@link DisplayFeature} is not valid then it will be omitted. + * Translate from the {@link CommonFoldingFeature} to + * {@link DisplayFeature} for a given {@link Activity}. If a + * {@link CommonFoldingFeature} is not valid then it will be omitted. * * For a {@link FoldingFeature} the bounds are localized into the {@link Activity} window - * coordinate space and the state is calculated either from {@link DisplayFeature#getState()} or - * {@link #mDisplayFeatureProducer}. The state from {@link #mDisplayFeatureProducer} may not be - * valid since {@link #mDisplayFeatureProducer} is a general state controller. If the state is - * not valid, the {@link FoldingFeature} is omitted from the {@link List} of - * {@link androidx.window.extensions.layout.DisplayFeature}. If the bounds are not valid, - * constructing a {@link FoldingFeature} will throw an {@link IllegalArgumentException} since - * this can cause negative UI effects down stream. + * coordinate space and the state is calculated from {@link CommonFoldingFeature#getState()}. + * The state from {@link #mFoldingFeatureProducer} may not be valid since + * {@link #mFoldingFeatureProducer} is a general state controller. If the state is not valid, + * the {@link FoldingFeature} is omitted from the {@link List} of {@link DisplayFeature}. If the + * bounds are not valid, constructing a {@link FoldingFeature} will throw an + * {@link IllegalArgumentException} since this can cause negative UI effects down stream. * * @param activity a proxy for the {@link android.view.Window} that contains the - * {@link androidx.window.extensions.layout.DisplayFeature}. - * @return a {@link List} of valid {@link androidx.window.extensions.layout.DisplayFeature} that + * {@link DisplayFeature}. + * @return a {@link List} of valid {@link DisplayFeature} that * are within the {@link android.view.Window} of the {@link Activity} */ - private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures( - @NonNull Activity activity) { - List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>(); + private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) { + List<DisplayFeature> features = new ArrayList<>(); int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); @@ -211,11 +193,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return features; } - Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); if (storedFeatures.isPresent()) { - - for (DisplayFeature baseFeature : storedFeatures.get()) { - Integer state = getFeatureState(baseFeature); + for (CommonFoldingFeature baseFeature : storedFeatures.get()) { + Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; } @@ -223,8 +204,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { rotateRectToDisplayRotation(displayId, featureRect); transformToWindowSpaceRect(activity, featureRect); - features.add(new FoldingFeature(featureRect, baseFeature.getType(), - getFeatureState(baseFeature))); + features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } } return features; @@ -232,13 +212,31 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private void updateRegistrations() { if (hasListeners()) { - mSettingsDevicePostureProducer.registerObserversIfNeeded(); mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); } else { - mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); } - onDisplayFeaturesChanged(); } + + private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + super.onActivityCreated(activity, savedInstanceState); + onDisplayFeaturesChangedIfListening(activity); + } + + @Override + public void onActivityConfigurationChanged(Activity activity) { + super.onActivityConfigurationChanged(activity); + onDisplayFeaturesChangedIfListening(activity); + } + + private void onDisplayFeaturesChangedIfListening(Activity activity) { + IBinder token = activity.getWindow().getAttributes().token; + if (token == null || isListeningForLayoutChanges(token)) { + onDisplayFeaturesChanged(); + } + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index aa949f126154..c7b709347060 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -23,16 +23,17 @@ import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.app.Activity; import android.app.ActivityThread; +import android.app.Application; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; import android.os.IBinder; import android.util.Log; import androidx.annotation.NonNull; -import androidx.window.common.DeviceStateManagerPostureProducer; -import androidx.window.common.DisplayFeature; -import androidx.window.common.ResourceConfigDisplayFeatureProducer; -import androidx.window.common.SettingsDevicePostureProducer; +import androidx.window.common.CommonFoldingFeature; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; +import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.SettingsDisplayFeatureProducer; import androidx.window.util.DataProducer; import androidx.window.util.PriorityDataProducer; @@ -48,36 +49,25 @@ import java.util.Optional; */ class SampleSidecarImpl extends StubSidecar { private static final String TAG = "SampleSidecar"; - private static final boolean DEBUG = false; - private final SettingsDevicePostureProducer mSettingsDevicePostureProducer; - private final DataProducer<Integer> mDevicePostureProducer; + private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; - private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer; - private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer; + private final SettingsDisplayFeatureProducer mSettingsFoldingFeatureProducer; SampleSidecarImpl(Context context) { - mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context); - mDevicePostureProducer = new PriorityDataProducer<>(List.of( - mSettingsDevicePostureProducer, - new DeviceStateManagerPostureProducer(context) + ((Application) context.getApplicationContext()) + .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); + mSettingsFoldingFeatureProducer = new SettingsDisplayFeatureProducer(context); + mFoldingFeatureProducer = new PriorityDataProducer<>(List.of( + mSettingsFoldingFeatureProducer, + new DeviceStateManagerFoldingFeatureProducer(context) )); - mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context); - mDisplayFeatureProducer = new PriorityDataProducer<>(List.of( - mSettingsDisplayFeatureProducer, - new ResourceConfigDisplayFeatureProducer(context) - )); - - mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged); - mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); - } - - private void onDevicePostureChanged() { - updateDeviceState(getDeviceState()); + mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } private void onDisplayFeaturesChanged() { + updateDeviceState(getDeviceState()); for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); updateWindowLayout(windowToken, newLayout); @@ -87,27 +77,21 @@ class SampleSidecarImpl extends StubSidecar { @NonNull @Override public SidecarDeviceState getDeviceState() { - Optional<Integer> posture = mDevicePostureProducer.getData(); - SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = posture.orElse(deviceStateFromFeature()); + deviceState.posture = deviceStateFromFeature(); return deviceState; } private int deviceStateFromFeature() { - List<DisplayFeature> storedFeatures = mDisplayFeatureProducer.getData() + List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData() .orElse(Collections.emptyList()); for (int i = 0; i < storedFeatures.size(); i++) { - DisplayFeature feature = storedFeatures.get(i); - final int state = feature.getState() == null ? -1 : feature.getState(); - if (DEBUG && feature.getState() == null) { - Log.d(TAG, "feature#getState was null for DisplayFeature: " + feature); - } - + CommonFoldingFeature feature = storedFeatures.get(i); + final int state = feature.getState(); switch (state) { - case DisplayFeature.COMMON_STATE_FLAT: + case CommonFoldingFeature.COMMON_STATE_FLAT: return SidecarDeviceState.POSTURE_OPENED; - case DisplayFeature.COMMON_STATE_HALF_OPENED: + case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: return SidecarDeviceState.POSTURE_HALF_OPENED; } } @@ -127,22 +111,22 @@ class SampleSidecarImpl extends StubSidecar { } private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { - List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>(); int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); - return features; + return Collections.emptyList(); } if (activity.isInMultiWindowMode()) { // It is recommended not to report any display features in multi-window mode, since it // won't be possible to synchronize the display feature positions with window movement. - return features; + return Collections.emptyList(); } - Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData(); + Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); + List<SidecarDisplayFeature> features = new ArrayList<>(); if (storedFeatures.isPresent()) { - for (DisplayFeature baseFeature : storedFeatures.get()) { + for (CommonFoldingFeature baseFeature : storedFeatures.get()) { SidecarDisplayFeature feature = new SidecarDisplayFeature(); Rect featureRect = baseFeature.getRect(); rotateRectToDisplayRotation(displayId, featureRect); @@ -152,17 +136,37 @@ class SampleSidecarImpl extends StubSidecar { features.add(feature); } } - return features; + return Collections.unmodifiableList(features); } @Override protected void onListenersChanged() { if (hasListeners()) { - mSettingsDevicePostureProducer.registerObserversIfNeeded(); - mSettingsDisplayFeatureProducer.registerObserversIfNeeded(); + mSettingsFoldingFeatureProducer.registerObserversIfNeeded(); + onDisplayFeaturesChanged(); } else { - mSettingsDevicePostureProducer.unregisterObserversIfNeeded(); - mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded(); + mSettingsFoldingFeatureProducer.unregisterObserversIfNeeded(); + } + } + + private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + super.onActivityCreated(activity, savedInstanceState); + onDisplayFeaturesChangedForActivity(activity); + } + + @Override + public void onActivityConfigurationChanged(Activity activity) { + super.onActivityConfigurationChanged(activity); + onDisplayFeaturesChangedForActivity(activity); + } + + private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) { + IBinder token = activity.getWindow().getAttributes().token; + if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) { + onDisplayFeaturesChanged(); + } } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java index 199c37315c07..b9c808a6569b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java @@ -30,7 +30,7 @@ import java.util.Set; abstract class StubSidecar implements SidecarInterface { private SidecarCallback mSidecarCallback; - private final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>(); + final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>(); private boolean mDeviceStateChangeListenerRegistered; StubSidecar() { diff --git a/libs/WindowManager/Shell/res/color/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_education_dismiss_button_background_ripple.xml new file mode 100644 index 000000000000..43cba1a37bc8 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/letterbox_education_dismiss_button_background_ripple.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml index 0d8a8faa9798..42572d64b96f 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml @@ -15,6 +15,6 @@ ~ limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@android:color/system_accent1_10"> + android:color="@color/letterbox_education_dismiss_button_background_ripple"> <item android:drawable="@drawable/letterbox_education_dismiss_button_background"/> </ripple>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml index 552048e8be9a..dcb4c1026062 100644 --- a/libs/WindowManager/Shell/res/values-television/config.xml +++ b/libs/WindowManager/Shell/res/values-television/config.xml @@ -33,4 +33,10 @@ <!-- The default gravity for the picture-in-picture window. Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT --> <integer name="config_defaultPictureInPictureGravity">0x55</integer> + + <!-- Fraction of screen width/height restricted keep clear areas can move the PiP. --> + <fraction name="config_pipMaxRestrictedMoveDistance">15%</fraction> + + <!-- Duration (in milliseconds) the PiP stays stashed before automatically unstashing. --> + <integer name="config_pipStashDuration">5000</integer> </resources> diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml new file mode 100644 index 000000000000..14e89f8b08df --- /dev/null +++ b/libs/WindowManager/Shell/res/values-television/dimen.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- These resources are around just to allow their values to be customized + for TV products. Do not translate. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Padding between PIP and keep clear areas that caused it to move. --> + <dimen name="pip_keep_clear_area_padding">16dp</dimen> +</resources> diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml index c7b8a130e141..09ed9b8e52ee 100644 --- a/libs/WindowManager/Shell/res/values/strings_tv.xml +++ b/libs/WindowManager/Shell/res/values/strings_tv.xml @@ -39,5 +39,10 @@ <!-- Button to collapse/shrink the picture-in-picture (PIP) window [CHAR LIMIT=30] --> <string name="pip_collapse">Collapse PIP</string> + + <!-- Educative text instructing the user to double press the HOME button to access the pip + controls menu [CHAR LIMIT=50] --> + <string name="pip_edu_text"> Double press <annotation icon="home_icon"> HOME </annotation> for + controls </string> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index 908a31dc3e4e..8483f070d06a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -21,6 +21,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; +import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; @@ -46,11 +47,13 @@ public final class ShellCommandHandlerImpl { private final Optional<AppPairsController> mAppPairsOptional; private final Optional<RecentTasksController> mRecentTasks; private final ShellTaskOrganizer mShellTaskOrganizer; + private final KidsModeTaskOrganizer mKidsModeTaskOrganizer; private final ShellExecutor mMainExecutor; private final HandlerImpl mImpl = new HandlerImpl(); public ShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, + KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, @@ -60,6 +63,7 @@ public final class ShellCommandHandlerImpl { Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { mShellTaskOrganizer = shellTaskOrganizer; + mKidsModeTaskOrganizer = kidsModeTaskOrganizer; mRecentTasks = recentTasks; mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; @@ -92,6 +96,9 @@ public final class ShellCommandHandlerImpl { pw.println(); pw.println(); mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, "")); + pw.println(); + pw.println(); + mKidsModeTaskOrganizer.dump(pw, ""); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index c3ce3627fb0b..b729fe1e55dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -29,6 +29,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.fullscreen.FullscreenUnfoldController; +import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -48,6 +49,7 @@ public class ShellInitImpl { private final DisplayInsetsController mDisplayInsetsController; private final DragAndDropController mDragAndDropController; private final ShellTaskOrganizer mShellTaskOrganizer; + private final KidsModeTaskOrganizer mKidsModeTaskOrganizer; private final Optional<BubbleController> mBubblesOptional; private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<AppPairsController> mAppPairsOptional; @@ -68,6 +70,7 @@ public class ShellInitImpl { DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, + KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<BubbleController> bubblesOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, @@ -84,6 +87,7 @@ public class ShellInitImpl { mDisplayInsetsController = displayInsetsController; mDragAndDropController = dragAndDropController; mShellTaskOrganizer = shellTaskOrganizer; + mKidsModeTaskOrganizer = kidsModeTaskOrganizer; mBubblesOptional = bubblesOptional; mSplitScreenOptional = splitScreenOptional; mAppPairsOptional = appPairsOptional; @@ -136,6 +140,9 @@ public class ShellInitImpl { mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init); mRecentTasks.ifPresent(RecentTasksController::init); + + // Initialize kids mode task organizer + mKidsModeTaskOrganizer.initialize(mStartingWindow); } @ExternalThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 3f8343a7abd3..84429948c643 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -196,8 +196,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } @VisibleForTesting - ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, - Context context, @Nullable CompatUIController compatUI, + protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, + ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI, Optional<RecentTasksController> recentTasks) { super(taskOrganizerController, mainExecutor); mCompatUI = compatUI; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index 79e624212f4b..3876533a922e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -173,8 +173,8 @@ public class BadgedImageView extends ConstraintLayout { } @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); + public void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); if (!shouldDrawDot()) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index d22fb5039052..4ba32e93fb3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -110,6 +110,14 @@ public class DisplayController { } /** + * Get the InsetsState of a display. + */ + public InsetsState getInsetsState(int displayId) { + final DisplayRecord r = mDisplays.get(displayId); + return r != null ? r.mInsetsState : null; + } + + /** * Updates the insets for a given display. */ public void updateDisplayInsets(int displayId, InsetsState state) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index 1e934c5c6e22..1dd5ebcd993e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -69,7 +69,8 @@ public abstract class TvPipModule { TaskStackListenerImpl taskStackListener, DisplayController displayController, WindowManagerShellWrapper windowManagerShellWrapper, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { return Optional.of( TvPipController.create( context, @@ -83,7 +84,8 @@ public abstract class TvPipModule { taskStackListener, displayController, windowManagerShellWrapper, - mainExecutor)); + mainExecutor, + mainHandler)); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 0362b3fba36b..bf0337dbd11a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -68,6 +68,7 @@ import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.fullscreen.FullscreenUnfoldController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; +import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHanded; @@ -184,7 +185,23 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) { + static KidsModeTaskOrganizer provideKidsModeTaskOrganizer( + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler, + Context context, + CompatUIController compatUI, + SyncTransactionQueue syncTransactionQueue, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + Optional<RecentTasksController> recentTasksOptional + ) { + return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, compatUI, + syncTransactionQueue, displayController, displayInsetsController, + recentTasksOptional); + } + + @WMSingleton + @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) { return Optional.of(compatUIController.asCompatUI()); } @@ -637,6 +654,7 @@ public abstract class WMShellBaseModule { DisplayInsetsController displayInsetsController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, + KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<BubbleController> bubblesOptional, Optional<SplitScreenController> splitScreenOptional, Optional<AppPairsController> appPairsOptional, @@ -653,6 +671,7 @@ public abstract class WMShellBaseModule { displayInsetsController, dragAndDropController, shellTaskOrganizer, + kidsModeTaskOrganizer, bubblesOptional, splitScreenOptional, appPairsOptional, @@ -680,6 +699,7 @@ public abstract class WMShellBaseModule { @Provides static ShellCommandHandlerImpl provideShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, + KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, @@ -688,7 +708,7 @@ public abstract class WMShellBaseModule { Optional<AppPairsController> appPairsOptional, Optional<RecentTasksController> recentTasksOptional, @ShellMainThread ShellExecutor mainExecutor) { - return new ShellCommandHandlerImpl(shellTaskOrganizer, + return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer, legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java new file mode 100644 index 000000000000..429eb9963794 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.kidsmode; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.window.ITaskOrganizerController; +import android.window.TaskAppearedInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.ForceShowNavigationBarSettingsObserver; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.startingsurface.StartingWindowController; + +import java.io.PrintWriter; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * A dedicated task organizer when kids mode is enabled. + * - Creates a root task with bounds that exclude the navigation bar area + * - Launch all task into the root task except for Launcher + */ +public class KidsModeTaskOrganizer extends ShellTaskOrganizer { + private static final String TAG = "KidsModeTaskOrganizer"; + + private static final int[] CONTROLLED_ACTIVITY_TYPES = + {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD}; + private static final int[] CONTROLLED_WINDOWING_MODES = + {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; + + private final Handler mMainHandler; + private final Context mContext; + private final SyncTransactionQueue mSyncQueue; + private final DisplayController mDisplayController; + private final DisplayInsetsController mDisplayInsetsController; + + @VisibleForTesting + ActivityManager.RunningTaskInfo mLaunchRootTask; + @VisibleForTesting + SurfaceControl mLaunchRootLeash; + @VisibleForTesting + final IBinder mCookie = new Binder(); + + private final InsetsState mInsetsState = new InsetsState(); + private int mDisplayWidth; + private int mDisplayHeight; + + private ForceShowNavigationBarSettingsObserver mForceShowNavigationBarSettingsObserver; + private boolean mEnabled; + + DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = + new DisplayController.OnDisplaysChangedListener() { + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + if (displayId != DEFAULT_DISPLAY) { + return; + } + final DisplayLayout displayLayout = + mDisplayController.getDisplayLayout(DEFAULT_DISPLAY); + if (displayLayout == null) { + return; + } + final int displayWidth = displayLayout.width(); + final int displayHeight = displayLayout.height(); + if (displayWidth == mDisplayWidth || displayHeight == mDisplayHeight) { + return; + } + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + updateBounds(); + } + }; + + DisplayInsetsController.OnInsetsChangedListener mOnInsetsChangedListener = + new DisplayInsetsController.OnInsetsChangedListener() { + @Override + public void insetsChanged(InsetsState insetsState) { + // Update bounds only when the insets of navigation bar or task bar is changed. + if (Objects.equals(insetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR), + mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR)) + && Objects.equals(insetsState.peekSource( + InsetsState.ITYPE_EXTRA_NAVIGATION_BAR), + mInsetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR))) { + return; + } + mInsetsState.set(insetsState); + updateBounds(); + } + }; + + @VisibleForTesting + KidsModeTaskOrganizer( + ITaskOrganizerController taskOrganizerController, + ShellExecutor mainExecutor, + Handler mainHandler, + Context context, + @Nullable CompatUIController compatUI, + SyncTransactionQueue syncTransactionQueue, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + Optional<RecentTasksController> recentTasks, + ForceShowNavigationBarSettingsObserver forceShowNavigationBarSettingsObserver) { + super(taskOrganizerController, mainExecutor, context, compatUI, recentTasks); + mContext = context; + mMainHandler = mainHandler; + mSyncQueue = syncTransactionQueue; + mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; + mForceShowNavigationBarSettingsObserver = forceShowNavigationBarSettingsObserver; + } + + public KidsModeTaskOrganizer( + ShellExecutor mainExecutor, + Handler mainHandler, + Context context, + @Nullable CompatUIController compatUI, + SyncTransactionQueue syncTransactionQueue, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + Optional<RecentTasksController> recentTasks) { + super(mainExecutor, context, compatUI, recentTasks); + mContext = context; + mMainHandler = mainHandler; + mSyncQueue = syncTransactionQueue; + mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; + } + + /** + * Initializes kids mode status. + */ + public void initialize(StartingWindowController startingWindowController) { + initStartingWindow(startingWindowController); + if (mForceShowNavigationBarSettingsObserver == null) { + mForceShowNavigationBarSettingsObserver = new ForceShowNavigationBarSettingsObserver( + mMainHandler, mContext); + } + mForceShowNavigationBarSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState()); + updateKidsModeState(); + mForceShowNavigationBarSettingsObserver.register(); + } + + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + if (mEnabled && mLaunchRootTask == null && taskInfo.launchCookies != null + && taskInfo.launchCookies.contains(mCookie)) { + mLaunchRootTask = taskInfo; + mLaunchRootLeash = leash; + updateTask(); + } + super.onTaskAppeared(taskInfo, leash); + + mSyncQueue.runInSync(t -> { + // Reset several properties back to fullscreen (PiP, for example, leaves all these + // properties in a bad state). + t.setCrop(leash, null); + t.setPosition(leash, 0, 0); + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + }); + } + + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + if (mLaunchRootTask != null && mLaunchRootTask.taskId == taskInfo.taskId + && !taskInfo.equals(mLaunchRootTask)) { + mLaunchRootTask = taskInfo; + } + + super.onTaskInfoChanged(taskInfo); + } + + @VisibleForTesting + void updateKidsModeState() { + final boolean enabled = mForceShowNavigationBarSettingsObserver.isEnabled(); + if (mEnabled == enabled) { + return; + } + mEnabled = enabled; + if (mEnabled) { + enable(); + } else { + disable(); + } + } + + @VisibleForTesting + void enable() { + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY); + if (displayLayout != null) { + mDisplayWidth = displayLayout.width(); + mDisplayHeight = displayLayout.height(); + } + mInsetsState.set(mDisplayController.getInsetsState(DEFAULT_DISPLAY)); + mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, + mOnInsetsChangedListener); + mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener); + List<TaskAppearedInfo> taskAppearedInfos = registerOrganizer(); + for (int i = 0; i < taskAppearedInfos.size(); i++) { + final TaskAppearedInfo info = taskAppearedInfos.get(i); + onTaskAppeared(info.getTaskInfo(), info.getLeash()); + } + createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, mCookie); + updateTask(); + } + + @VisibleForTesting + void disable() { + mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY, + mOnInsetsChangedListener); + mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener); + updateTask(); + final WindowContainerToken token = mLaunchRootTask.token; + if (token != null) { + deleteRootTask(token); + } + mLaunchRootTask = null; + mLaunchRootLeash = null; + unregisterOrganizer(); + } + + private void updateTask() { + updateTask(getWindowContainerTransaction()); + } + + private void updateTask(WindowContainerTransaction wct) { + if (mLaunchRootTask == null || mLaunchRootLeash == null) { + return; + } + final Rect taskBounds = calculateBounds(); + final WindowContainerToken rootToken = mLaunchRootTask.token; + wct.setBounds(rootToken, mEnabled ? taskBounds : null); + wct.setLaunchRoot(rootToken, + mEnabled ? CONTROLLED_WINDOWING_MODES : null, + mEnabled ? CONTROLLED_ACTIVITY_TYPES : null); + wct.reparentTasks( + mEnabled ? null : rootToken /* currentParent */, + mEnabled ? rootToken : null /* newParent */, + CONTROLLED_WINDOWING_MODES, + CONTROLLED_ACTIVITY_TYPES, + true /* onTop */); + wct.reorder(rootToken, mEnabled /* onTop */); + mSyncQueue.queue(wct); + final SurfaceControl rootLeash = mLaunchRootLeash; + mSyncQueue.runInSync(t -> { + t.setPosition(rootLeash, taskBounds.left, taskBounds.top); + t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height()); + }); + } + + private Rect calculateBounds() { + final Rect bounds = new Rect(0, 0, mDisplayWidth, mDisplayHeight); + final InsetsSource navBarSource = mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR); + final InsetsSource taskBarSource = mInsetsState.peekSource( + InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + if (navBarSource != null && !navBarSource.getFrame().isEmpty()) { + bounds.inset(navBarSource.calculateInsets(bounds, false /* ignoreVisibility */)); + } else if (taskBarSource != null && !taskBarSource.getFrame().isEmpty()) { + bounds.inset(taskBarSource.calculateInsets(bounds, false /* ignoreVisibility */)); + } else { + bounds.setEmpty(); + } + return bounds; + } + + private void updateBounds() { + if (mLaunchRootTask == null) { + return; + } + final WindowContainerTransaction wct = getWindowContainerTransaction(); + final Rect taskBounds = calculateBounds(); + wct.setBounds(mLaunchRootTask.token, taskBounds); + mSyncQueue.queue(wct); + final SurfaceControl finalLeash = mLaunchRootLeash; + mSyncQueue.runInSync(t -> { + t.setPosition(finalLeash, taskBounds.left, taskBounds.top); + t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height()); + }); + } + + @VisibleForTesting + WindowContainerTransaction getWindowContainerTransaction() { + return new WindowContainerTransaction(); + } + + @Override + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + " mEnabled=" + mEnabled); + pw.println(innerPrefix + " mLaunchRootTask=" + mLaunchRootTask); + pw.println(innerPrefix + " mLaunchRootLeash=" + mLaunchRootLeash); + pw.println(innerPrefix + " mDisplayWidth=" + mDisplayWidth); + pw.println(innerPrefix + " mDisplayHeight=" + mDisplayHeight); + pw.println(innerPrefix + " mInsetsState=" + mInsetsState); + super.dump(pw, innerPrefix); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index 797df413d262..c2d582354b13 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -74,7 +74,7 @@ public class PipBoundsAlgorithm { /** * TODO: move the resources to SysUI package. */ - protected void reloadResources(Context context) { + private void reloadResources(Context context) { final Resources res = context.getResources(); mDefaultAspectRatio = res.getFloat( R.dimen.config_pictureInPictureDefaultAspectRatio); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index ddcd4bd3a8e3..17d7f5d0d567 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -55,11 +55,15 @@ public class PipBoundsState { public static final int STASH_TYPE_NONE = 0; public static final int STASH_TYPE_LEFT = 1; public static final int STASH_TYPE_RIGHT = 2; + public static final int STASH_TYPE_BOTTOM = 3; + public static final int STASH_TYPE_TOP = 4; @IntDef(prefix = { "STASH_TYPE_" }, value = { STASH_TYPE_NONE, STASH_TYPE_LEFT, - STASH_TYPE_RIGHT + STASH_TYPE_RIGHT, + STASH_TYPE_BOTTOM, + STASH_TYPE_TOP }) @Retention(RetentionPolicy.SOURCE) public @interface StashType {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 401dc9ae02e1..be713a59a816 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -152,7 +152,7 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); + final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); mInFixedRotation = fixedRotationChange != null; mEndFixedRotation = mInFixedRotation @@ -172,22 +172,29 @@ public class PipTransition extends PipTransitionController { throw new RuntimeException("Previous callback not called, aborting exit PIP."); } - if (currentPipChange == null) { - throw new RuntimeException("Cannot find the pip window for exit-pip transition."); + // PipTaskChange can be null if the PIP task has been detached, for example, when the + // task contains multiple activities, the PIP will be moved to a new PIP task when + // entering, and be moved back when exiting. In that case, the PIP task will be removed + // immediately. + final TaskInfo pipTaskInfo = currentPipTaskChange != null + ? currentPipTaskChange.getTaskInfo() + : mPipOrganizer.getTaskInfo(); + if (pipTaskInfo == null) { + throw new RuntimeException("Cannot find the pip task for exit-pip transition."); } switch (type) { case TRANSIT_EXIT_PIP: startExitAnimation(info, startTransaction, finishTransaction, finishCallback, - currentPipChange); + pipTaskInfo, currentPipTaskChange); break; case TRANSIT_EXIT_PIP_TO_SPLIT: startExitToSplitAnimation(info, startTransaction, finishTransaction, - finishCallback, currentPipChange); + finishCallback, pipTaskInfo); break; case TRANSIT_REMOVE_PIP: removePipImmediately(info, startTransaction, finishTransaction, finishCallback, - currentPipChange); + pipTaskInfo); break; default: throw new IllegalStateException("mExitTransition with unexpected transit type=" @@ -200,9 +207,9 @@ public class PipTransition extends PipTransitionController { // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can // happen when a new activity requests enter PIP). In this case, we just show this Task in // its end state, and play other animation as normal. - if (currentPipChange != null - && currentPipChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) { - resetPrevPip(currentPipChange, startTransaction); + if (currentPipTaskChange != null + && currentPipTaskChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) { + resetPrevPip(currentPipTaskChange, startTransaction); } // Entering PIP. @@ -212,8 +219,9 @@ public class PipTransition extends PipTransitionController { // For transition that we don't animate, but contains the PIP leash, we need to update the // PIP surface, otherwise it will be reset after the transition. - if (currentPipChange != null) { - updatePipForUnhandledTransition(currentPipChange, startTransaction, finishTransaction); + if (currentPipTaskChange != null) { + updatePipForUnhandledTransition(currentPipTaskChange, startTransaction, + finishTransaction); } // Fade in the fadeout PIP when the fixed rotation is finished. @@ -322,7 +330,7 @@ public class PipTransition extends PipTransitionController { } @Nullable - private TransitionInfo.Change findCurrentPipChange(@NonNull TransitionInfo info) { + private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) { if (mCurrentPipTaskToken == null) { return null; } @@ -350,9 +358,30 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull TransitionInfo.Change pipChange) { + @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) { + TransitionInfo.Change pipChange = pipTaskChange; + if (pipChange == null) { + // The pipTaskChange is null, this can happen if we are reparenting the PIP activity + // back to its original Task. In that case, we should animate the activity leash + // instead, which should be the only non-task, independent, TRANSIT_CHANGE window. + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() == null && change.getMode() == TRANSIT_CHANGE + && TransitionInfo.isIndependent(change, info)) { + pipChange = change; + break; + } + } + } + if (pipChange == null) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: No window of exiting PIP is found. Can't play expand animation", TAG); + removePipImmediately(info, startTransaction, finishTransaction, finishCallback, + taskInfo); + return; + } mFinishCallback = (wct, wctCB) -> { - mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); + mPipOrganizer.onExitPipFinished(taskInfo); finishCallback.onTransitionFinished(wct, wctCB); }; mFinishTransaction = finishTransaction; @@ -372,7 +401,7 @@ public class PipTransition extends PipTransitionController { if (displayRotationChange != null) { // Exiting PIP to fullscreen with orientation change. startExpandAndRotationAnimation(info, startTransaction, finishTransaction, - displayRotationChange, pipChange); + displayRotationChange, taskInfo, pipChange); return; } } @@ -413,15 +442,14 @@ public class PipTransition extends PipTransitionController { } else { rotationDelta = Surface.ROTATION_0; } - startExpandAnimation(pipChange.getTaskInfo(), pipChange.getLeash(), destinationBounds, - rotationDelta); + startExpandAnimation(taskInfo, pipChange.getLeash(), destinationBounds, rotationDelta); } private void startExpandAndRotationAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionInfo.Change displayRotationChange, - @NonNull TransitionInfo.Change pipChange) { + @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange) { final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(), displayRotationChange.getEndRotation()); @@ -459,7 +487,7 @@ public class PipTransition extends PipTransitionController { // Expand and rotate the pip window to fullscreen. final PipAnimationController.PipTransitionAnimator animator = - mPipAnimationController.getAnimator(pipChange.getTaskInfo(), pipChange.getLeash(), + mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(), startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, pipRotateDelta); animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) @@ -485,11 +513,11 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull TransitionInfo.Change pipChange) { + @NonNull TaskInfo taskInfo) { startTransaction.apply(); finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), mPipBoundsState.getDisplayBounds()); - mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); + mPipOrganizer.onExitPipFinished(taskInfo); finishCallback.onTransitionFinished(null, null); } @@ -689,11 +717,11 @@ public class PipTransition extends PipTransitionController { } } - private void startExitToSplitAnimation(TransitionInfo info, - SurfaceControl.Transaction startTransaction, - SurfaceControl.Transaction finishTransaction, - Transitions.TransitionFinishCallback finishCallback, - TransitionInfo.Change pipChange) { + private void startExitToSplitAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull TaskInfo taskInfo) { final int changeSize = info.getChanges().size(); if (changeSize < 4) { throw new RuntimeException( @@ -721,15 +749,15 @@ public class PipTransition extends PipTransitionController { mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction); startTransaction.apply(); - mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); + mPipOrganizer.onExitPipFinished(taskInfo); finishCallback.onTransitionFinished(null, null); } - private void resetPrevPip(@NonNull TransitionInfo.Change prevPipChange, + private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange, @NonNull SurfaceControl.Transaction startTransaction) { - final SurfaceControl leash = prevPipChange.getLeash(); - final Rect bounds = prevPipChange.getEndAbsBounds(); - final Point offset = prevPipChange.getEndRelOffset(); + final SurfaceControl leash = prevPipTaskChange.getLeash(); + final Rect bounds = prevPipTaskChange.getEndAbsBounds(); + final Point offset = prevPipTaskChange.getEndRelOffset(); bounds.offset(-offset.x, -offset.y); startTransaction.setWindowCrop(leash, null); @@ -737,7 +765,7 @@ public class PipTransition extends PipTransitionController { startTransaction.setCornerRadius(leash, 0); startTransaction.setPosition(leash, bounds.left, bounds.top); - if (mHasFadeOut && prevPipChange.getTaskInfo().isVisible()) { + if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) { if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); } @@ -745,7 +773,7 @@ public class PipTransition extends PipTransitionController { } mHasFadeOut = false; mCurrentPipTaskToken = null; - mPipOrganizer.onExitPipFinished(prevPipChange.getTaskInfo()); + mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo()); } private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index 8ab78e64e2f6..72b934805c95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -28,15 +28,21 @@ import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; +import android.os.SystemClock; +import android.util.ArraySet; import android.util.Log; import android.util.Size; import android.view.Gravity; import androidx.annotation.NonNull; +import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipSnapAlgorithm; +import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; + +import java.util.Set; /** * Contains pip bounds calculations that are specific to TV. @@ -46,91 +52,129 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName(); private static final boolean DEBUG = TvPipController.DEBUG; - private final @android.annotation.NonNull TvPipBoundsState mTvPipBoundsState; + private final @NonNull TvPipBoundsState mTvPipBoundsState; private int mFixedExpandedHeightInPx; private int mFixedExpandedWidthInPx; + private final TvPipKeepClearAlgorithm mKeepClearAlgorithm; + public TvPipBoundsAlgorithm(Context context, @NonNull TvPipBoundsState tvPipBoundsState, @NonNull PipSnapAlgorithm pipSnapAlgorithm) { super(context, tvPipBoundsState, pipSnapAlgorithm); this.mTvPipBoundsState = tvPipBoundsState; + this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(SystemClock::uptimeMillis); + reloadResources(context); } - @Override - protected void reloadResources(Context context) { - super.reloadResources(context); + private void reloadResources(Context context) { final Resources res = context.getResources(); mFixedExpandedHeightInPx = res.getDimensionPixelSize( com.android.internal.R.dimen.config_pictureInPictureExpandedHorizontalHeight); mFixedExpandedWidthInPx = res.getDimensionPixelSize( com.android.internal.R.dimen.config_pictureInPictureExpandedVerticalWidth); + mKeepClearAlgorithm.setPipAreaPadding( + res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding)); + mKeepClearAlgorithm.setMaxRestrictedDistanceFraction( + res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1)); + mKeepClearAlgorithm.setStashDuration(res.getInteger(R.integer.config_pipStashDuration)); + } + + @Override + public void onConfigurationChanged(Context context) { + super.onConfigurationChanged(context); + reloadResources(context); } /** Returns the destination bounds to place the PIP window on entry. */ @Override public Rect getEntryDestinationBounds() { if (DEBUG) Log.d(TAG, "getEntryDestinationBounds()"); - if (mTvPipBoundsState.getTvExpandedAspectRatio() != 0 + if (mTvPipBoundsState.isTvExpandedPipSupported() + && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 && !mTvPipBoundsState.isTvPipManuallyCollapsed()) { - updatePositionOnExpandToggled(Gravity.NO_GRAVITY, true); + updateExpandedPipSize(); + updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true); + mTvPipBoundsState.setTvPipExpanded(true); } - return getTvPipBounds(true); + return getTvPipBounds().getBounds(); } /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ @Override public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) { if (DEBUG) Log.d(TAG, "getAdjustedDestinationBounds: " + newAspectRatio); - return getTvPipBounds(mTvPipBoundsState.isTvPipExpanded()); + return getTvPipBounds().getBounds(); } /** - * The normal bounds at a different position on the screen. + * Calculates the PiP bounds. */ - public Rect getTvNormalBounds() { - Rect normalBounds = getNormalBounds(); - Rect insetBounds = new Rect(); + public Placement getTvPipBounds() { + final Size pipSize = getPipSize(); + final Rect displayBounds = mTvPipBoundsState.getDisplayBounds(); + final Size screenSize = new Size(displayBounds.width(), displayBounds.height()); + final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); + Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); + Set<Rect> unrestrictedKeepClearAreas = mTvPipBoundsState.getUnrestrictedKeepClearAreas(); + if (mTvPipBoundsState.isImeShowing()) { if (DEBUG) Log.d(TAG, "IME showing, height: " + mTvPipBoundsState.getImeHeight()); - insetBounds.bottom -= mTvPipBoundsState.getImeHeight(); + + final Rect imeBounds = new Rect( + 0, + insetBounds.bottom - mTvPipBoundsState.getImeHeight(), + insetBounds.right, + insetBounds.bottom); + + unrestrictedKeepClearAreas = new ArraySet<>(unrestrictedKeepClearAreas); + unrestrictedKeepClearAreas.add(imeBounds); } - Rect result = new Rect(); - Gravity.apply(mTvPipBoundsState.getTvPipGravity(), normalBounds.width(), - normalBounds.height(), insetBounds, result); + mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity()); + mKeepClearAlgorithm.setScreenSize(screenSize); + mKeepClearAlgorithm.setMovementBounds(insetBounds); + mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset()); + + final Placement placement = mKeepClearAlgorithm.calculatePipPosition( + pipSize, + restrictedKeepClearAreas, + unrestrictedKeepClearAreas); if (DEBUG) { - Log.d(TAG, "normalBounds: " + normalBounds.toShortString()); + Log.d(TAG, "pipSize: " + pipSize); + Log.d(TAG, "screenSize: " + screenSize); + Log.d(TAG, "stashOffset: " + mTvPipBoundsState.getStashOffset()); Log.d(TAG, "insetBounds: " + insetBounds.toShortString()); + Log.d(TAG, "pipSize: " + pipSize); Log.d(TAG, "gravity: " + Gravity.toString(mTvPipBoundsState.getTvPipGravity())); - Log.d(TAG, "resultBounds: " + result.toShortString()); + Log.d(TAG, "restrictedKeepClearAreas: " + restrictedKeepClearAreas); + Log.d(TAG, "unrestrictedKeepClearAreas: " + unrestrictedKeepClearAreas); + Log.d(TAG, "placement: " + placement); } - mTvPipBoundsState.setTvPipExpanded(false); - - return result; + return placement; } /** - * @return previous gravity if it is to be saved, or Gravity.NO_GRAVITY if not. + * @return previous gravity if it is to be saved, or {@link Gravity#NO_GRAVITY} if not. */ - int updatePositionOnExpandToggled(int previousGravity, boolean expanding) { + int updateGravityOnExpandToggled(int previousGravity, boolean expanding) { if (DEBUG) { - Log.d(TAG, "updatePositionOnExpandToggle(), expanding: " + expanding + Log.d(TAG, "updateGravityOnExpandToggled(), expanding: " + expanding + ", mOrientation: " + mTvPipBoundsState.getTvFixedPipOrientation() + ", previous gravity: " + Gravity.toString(previousGravity)); } - if (!mTvPipBoundsState.isTvExpandedPipEnabled()) { + if (!mTvPipBoundsState.isTvExpandedPipSupported()) { return Gravity.NO_GRAVITY; } if (expanding && mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_UNDETERMINED) { - float expandedRatio = mTvPipBoundsState.getTvExpandedAspectRatio(); + float expandedRatio = mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); if (expandedRatio == 0) { return Gravity.NO_GRAVITY; } @@ -139,7 +183,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { } else { mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_HORIZONTAL); } - } int gravityToSave = Gravity.NO_GRAVITY; @@ -181,10 +224,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { } /** - * @return true if position changed + * @return true if gravity changed */ - boolean updatePosition(int keycode) { - if (DEBUG) Log.d(TAG, "updatePosition, keycode: " + keycode); + boolean updateGravity(int keycode) { + if (DEBUG) Log.d(TAG, "updateGravity, keycode: " + keycode); // Check if position change is valid if (mTvPipBoundsState.isTvPipExpanded()) { @@ -247,26 +290,31 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { return false; } - /** - * Calculates the PiP bounds. - */ - public Rect getTvPipBounds(boolean expandedIfPossible) { - if (DEBUG) { - Log.d(TAG, "getExpandedBoundsIfPossible with gravity " - + Gravity.toString(mTvPipBoundsState.getTvPipGravity()) - + ", fixed orientation: " + mTvPipBoundsState.getTvFixedPipOrientation()); + private Size getPipSize() { + final boolean isExpanded = + mTvPipBoundsState.isTvExpandedPipSupported() && mTvPipBoundsState.isTvPipExpanded() + && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0; + if (isExpanded) { + return mTvPipBoundsState.getTvExpandedSize(); + } else { + final Rect normalBounds = getNormalBounds(); + return new Size(normalBounds.width(), normalBounds.height()); } + } - if (!mTvPipBoundsState.isTvExpandedPipEnabled() || !expandedIfPossible) { - return getTvNormalBounds(); - } + /** + * Updates {@link TvPipBoundsState#getTvExpandedSize()} based on + * {@link TvPipBoundsState#getDesiredTvExpandedAspectRatio()}, the screen size. + */ + void updateExpandedPipSize() { + final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout(); + final float expandedRatio = + mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height - DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout(); - float expandedRatio = mTvPipBoundsState.getTvExpandedAspectRatio(); // width / height - Size expandedSize; + final Size expandedSize; if (expandedRatio == 0) { - Log.d(TAG, "Expanded mode not supported"); - return getTvNormalBounds(); + Log.d(TAG, "updateExpandedPipSize(): Expanded mode aspect ratio of 0 not supported"); + return; } else if (expandedRatio < 1) { // vertical if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { @@ -300,26 +348,14 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { } } - if (expandedSize == null) { - return getTvNormalBounds(); - } - + mTvPipBoundsState.setTvExpandedSize(expandedSize); if (DEBUG) { - Log.d(TAG, "expanded size, width: " + expandedSize.getWidth() - + ", height: " + expandedSize.getHeight()); + Log.d(TAG, "updateExpandedPipSize(): expanded size, width=" + expandedSize.getWidth() + + ", height=" + expandedSize.getHeight()); } - - Rect insetBounds = new Rect(); - getInsetBounds(insetBounds); - - Rect expandedBounds = new Rect(); - Gravity.apply(mTvPipBoundsState.getTvPipGravity(), expandedSize.getWidth(), - expandedSize.getHeight(), insetBounds, expandedBounds); - if (DEBUG) Log.d(TAG, "expanded bounds: " + expandedBounds.toShortString()); - - mTvPipBoundsState.setTvExpandedSize(expandedSize); - mTvPipBoundsState.setTvPipExpanded(true); - return expandedBounds; } + void keepUnstashedForCurrentKeepClearAreas() { + mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index 9370e33fce65..d880f821ee73 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -53,10 +53,10 @@ public class TvPipBoundsState extends PipBoundsState { public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT; - private boolean mIsTvExpandedPipEnabled; + private final boolean mIsTvExpandedPipSupported; private boolean mIsTvPipExpanded; private boolean mTvPipManuallyCollapsed; - private float mTvExpandedAspectRatio; + private float mDesiredTvExpandedAspectRatio; private @Orientation int mTvFixedPipOrientation; private int mTvPipGravity; private @Nullable Size mTvExpandedSize; @@ -64,8 +64,8 @@ public class TvPipBoundsState extends PipBoundsState { public TvPipBoundsState(@NonNull Context context) { super(context); - setIsTvExpandedPipEnabled(context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE)); + mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE); } /** @@ -75,7 +75,7 @@ public class TvPipBoundsState extends PipBoundsState { public void setBoundsStateForEntry(ComponentName componentName, ActivityInfo activityInfo, PictureInPictureParams params, PipBoundsAlgorithm pipBoundsAlgorithm) { super.setBoundsStateForEntry(componentName, activityInfo, params, pipBoundsAlgorithm); - setTvExpandedAspectRatio(params.getExpandedAspectRatio(), true); + setDesiredTvExpandedAspectRatio(params.getExpandedAspectRatio(), true); } /** Resets the TV PiP state for a new activity. */ @@ -85,32 +85,32 @@ public class TvPipBoundsState extends PipBoundsState { } /** Set the tv expanded bounds of PIP */ - public void setTvExpandedSize(@Nullable Size bounds) { - mTvExpandedSize = bounds; + public void setTvExpandedSize(@Nullable Size size) { + mTvExpandedSize = size; } - /** Get the PIP tv expanded bounds. */ + /** Get the expanded size of the PiP. */ @Nullable public Size getTvExpandedSize() { return mTvExpandedSize; } /** Set the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */ - public void setTvExpandedAspectRatio(float aspectRatio, boolean override) { + public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) { if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED || aspectRatio == 0) { - mTvExpandedAspectRatio = aspectRatio; + mDesiredTvExpandedAspectRatio = aspectRatio; resetTvPipState(); return; } if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL) || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)) { - mTvExpandedAspectRatio = aspectRatio; + mDesiredTvExpandedAspectRatio = aspectRatio; } } /** Get the PIP aspect ratio for the expanded PIP (TV) that is desired by the app. */ - public float getTvExpandedAspectRatio() { - return mTvExpandedAspectRatio; + public float getDesiredTvExpandedAspectRatio() { + return mDesiredTvExpandedAspectRatio; } /** Sets the orientation the expanded TV PiP activity has been fixed to. */ @@ -154,13 +154,9 @@ public class TvPipBoundsState extends PipBoundsState { return mTvPipManuallyCollapsed; } - /** Sets whether expanded PiP is supported by the device. */ - public void setIsTvExpandedPipEnabled(boolean enabled) { - mIsTvExpandedPipEnabled = enabled; - } - /** Returns whether expanded PiP is supported by the device. */ - public boolean isTvExpandedPipEnabled() { - return mIsTvExpandedPipEnabled; + public boolean isTvExpandedPipSupported() { + return mIsTvExpandedPipSupported; } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 3c830e0a0d62..03c5e98de298 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -30,9 +30,9 @@ import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; +import android.os.Handler; import android.os.RemoteException; import android.util.Log; -import android.view.DisplayInfo; import android.view.Gravity; import com.android.wm.shell.R; @@ -45,9 +45,11 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -62,6 +64,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private static final String TAG = "TvPipController"; static final boolean DEBUG = false; + private static final double EPS = 1e-7; private static final int NONEXISTENT_TASK_ID = -1; @Retention(RetentionPolicy.SOURCE) @@ -97,11 +100,13 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final TvPipNotificationController mPipNotificationController; private final TvPipMenuController mTvPipMenuController; private final ShellExecutor mMainExecutor; + private final Handler mMainHandler; private final TvPipImpl mImpl = new TvPipImpl(); private @State int mState = STATE_NO_PIP; private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY; private int mPinnedTaskId = NONEXISTENT_TASK_ID; + private Runnable mUnstashRunnable; private int mResizeAnimationDuration; @@ -117,7 +122,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal TaskStackListenerImpl taskStackListener, DisplayController displayController, WindowManagerShellWrapper wmShell, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, + Handler mainHandler) { return new TvPipController( context, tvPipBoundsState, @@ -130,7 +136,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal taskStackListener, displayController, wmShell, - mainExecutor).mImpl; + mainExecutor, + mainHandler).mImpl; } private TvPipController( @@ -145,9 +152,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal TaskStackListenerImpl taskStackListener, DisplayController displayController, WindowManagerShellWrapper wmShell, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, + Handler mainHandler) { mContext = context; mMainExecutor = mainExecutor; + mMainHandler = mainHandler; mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState.setDisplayId(context.getDisplayId()); @@ -182,6 +191,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal loadConfigurations(); mPipNotificationController.onConfigurationChanged(mContext); + mTvPipBoundsAlgorithm.onConfigurationChanged(mContext); } /** @@ -206,13 +216,20 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } setState(STATE_PIP_MENU); - movePinnedStack(); + updatePinnedStackBounds(); } @Override public void closeMenu() { if (DEBUG) Log.d(TAG, "closeMenu(), state before=" + stateToName(mState)); setState(STATE_PIP); + mTvPipBoundsAlgorithm.keepUnstashedForCurrentKeepClearAreas(); + updatePinnedStackBounds(); + } + + @Override + public void onInMoveModeChanged() { + updatePinnedStackBounds(); } /** @@ -231,21 +248,21 @@ public class TvPipController implements PipTransitionController.PipTransitionCal if (DEBUG) Log.d(TAG, "togglePipExpansion()"); boolean expanding = !mTvPipBoundsState.isTvPipExpanded(); int saveGravity = mTvPipBoundsAlgorithm - .updatePositionOnExpandToggled(mPreviousGravity, expanding); + .updateGravityOnExpandToggled(mPreviousGravity, expanding); if (saveGravity != Gravity.NO_GRAVITY) { mPreviousGravity = saveGravity; } mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding); mTvPipBoundsState.setTvPipExpanded(expanding); - movePinnedStack(); + updatePinnedStackBounds(); } @Override public void movePip(int keycode) { - if (mTvPipBoundsAlgorithm.updatePosition(keycode)) { + if (mTvPipBoundsAlgorithm.updateGravity(keycode)) { mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity()); mPreviousGravity = Gravity.NO_GRAVITY; - movePinnedStack(); + updatePinnedStackBounds(); } else { if (DEBUG) Log.d(TAG, "Position hasn't changed"); } @@ -265,20 +282,48 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Set<Rect> unrestricted) { if (mTvPipBoundsState.getDisplayId() == displayId) { mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted); - movePinnedStack(); + updatePinnedStackBounds(); } } /** - * Animate to the updated position of the PiP based on the state and position of the PiP. + * Update the PiP bounds based on the state of the PiP and keep clear areas. + * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary. */ - private void movePinnedStack() { + private void updatePinnedStackBounds() { if (mState == STATE_NO_PIP) { return; } - Rect bounds = mTvPipBoundsAlgorithm.getTvPipBounds(mTvPipBoundsState.isTvPipExpanded()); - if (DEBUG) Log.d(TAG, "movePinnedStack() - new pip bounds: " + bounds.toShortString()); + final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode(); + final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition; + final Placement placement = mTvPipBoundsAlgorithm.getTvPipBounds(); + + int stashType = + disallowStashing ? PipBoundsState.STASH_TYPE_NONE : placement.getStashType(); + mTvPipBoundsState.setStashed(stashType); + + if (stayAtAnchorPosition) { + movePinnedStackTo(placement.getAnchorBounds()); + } else if (disallowStashing) { + movePinnedStackTo(placement.getUnstashedBounds()); + } else { + movePinnedStackTo(placement.getBounds()); + } + + if (mUnstashRunnable != null) { + mMainHandler.removeCallbacks(mUnstashRunnable); + mUnstashRunnable = null; + } + if (!disallowStashing && placement.getUnstashDestinationBounds() != null) { + mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds()); + mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime()); + } + } + + /** Animates the PiP to the given bounds. */ + private void movePinnedStackTo(Rect bounds) { + if (DEBUG) Log.d(TAG, "movePinnedStackTo() - new pip bounds: " + bounds.toShortString()); mPipTaskOrganizer.scheduleAnimateResizePip(bounds, mResizeAnimationDuration, rect -> { if (DEBUG) Log.d(TAG, "movePinnedStack() animation done"); @@ -359,6 +404,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal if (DEBUG) Log.d(TAG, " > show menu"); mTvPipMenuController.showMenu(); } + + updatePinnedStackBounds(); } private void loadConfigurations() { @@ -366,12 +413,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration); } - private DisplayInfo getDisplayInfo() { - final DisplayInfo displayInfo = new DisplayInfo(); - mContext.getDisplay().getDisplayInfo(displayInfo); - return displayInfo; - } - private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) { taskStackListener.addListener(new TaskStackListenerCallback() { @Override @@ -417,7 +458,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mTvPipBoundsState.setImeVisibility(imeVisible, imeHeight); if (mState != STATE_NO_PIP) { - movePinnedStack(); + updatePinnedStackBounds(); } } @@ -429,7 +470,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mTvPipBoundsState.setAspectRatio(ratio); if (!mTvPipBoundsState.isTvPipExpanded() && ratioChanged) { - movePinnedStack(); + updatePinnedStackBounds(); } } @@ -438,41 +479,45 @@ public class TvPipController implements PipTransitionController.PipTransitionCal if (DEBUG) Log.d(TAG, "onExpandedAspectRatioChanged: " + ratio); // 0) No update to the ratio --> don't do anything - if (mTvPipBoundsState.getTvExpandedAspectRatio() == ratio) { + + if (Math.abs(mTvPipBoundsState.getDesiredTvExpandedAspectRatio() - ratio) + < EPS) { return; } - mTvPipBoundsState.setTvExpandedAspectRatio(ratio, false); + mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false); // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled // --> update bounds, but don't toggle if (mTvPipBoundsState.isTvPipExpanded() && ratio != 0) { - movePinnedStack(); + mTvPipBoundsAlgorithm.updateExpandedPipSize(); + updatePinnedStackBounds(); } // 2) PiP is expanded, but expanded PiP was disabled // --> collapse PiP if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) { int saveGravity = mTvPipBoundsAlgorithm - .updatePositionOnExpandToggled(mPreviousGravity, false); + .updateGravityOnExpandToggled(mPreviousGravity, false); if (saveGravity != Gravity.NO_GRAVITY) { mPreviousGravity = saveGravity; } mTvPipBoundsState.setTvPipExpanded(false); - movePinnedStack(); + updatePinnedStackBounds(); } // 3) PiP not expanded and not manually collapsed and expand was enabled // --> expand to new ratio if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0 && !mTvPipBoundsState.isTvPipManuallyCollapsed()) { + mTvPipBoundsAlgorithm.updateExpandedPipSize(); int saveGravity = mTvPipBoundsAlgorithm - .updatePositionOnExpandToggled(mPreviousGravity, true); + .updateGravityOnExpandToggled(mPreviousGravity, true); if (saveGravity != Gravity.NO_GRAVITY) { mPreviousGravity = saveGravity; } mTvPipBoundsState.setTvPipExpanded(true); - movePinnedStack(); + updatePinnedStackBounds(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt new file mode 100644 index 000000000000..5ac7a7200494 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip.tv + +import android.graphics.Point +import android.graphics.Rect +import android.util.Size +import android.view.Gravity +import com.android.wm.shell.pip.PipBoundsState +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt + +private const val DEFAULT_PIP_MARGINS = 48 +private const val DEFAULT_STASH_DURATION = 5000L +private const val RELAX_DEPTH = 1 +private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15 + +/** + * This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking + * into account app defined keep clear areas. + * + * @param clock A function returning a current timestamp (in milliseconds) + */ +class TvPipKeepClearAlgorithm(private val clock: () -> Long) { + /** + * Result of the positioning algorithm. + * + * @param bounds The bounds the PiP should be placed at + * @param anchorBounds The bounds of the PiP anchor position + * (where the PiP would be placed if there were no keep clear areas) + * @param stashType Where the PiP has been stashed, if at all + * @param unstashDestinationBounds If stashed, the PiP should move to this position after + * [stashDuration] has passed. + * @param unstashTime If stashed, the time at which the PiP should move + * to [unstashDestinationBounds] + */ + data class Placement( + val bounds: Rect, + val anchorBounds: Rect, + @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE, + val unstashDestinationBounds: Rect? = null, + val unstashTime: Long = 0L + ) { + /** Bounds to use if the PiP should not be stashed. */ + fun getUnstashedBounds() = unstashDestinationBounds ?: bounds + } + + /** The size of the screen */ + private var screenSize = Size(0, 0) + + /** The bounds the PiP is allowed to move in */ + private var movementBounds = Rect() + + /** Padding to add between a keep clear area that caused the PiP to move and the PiP */ + var pipAreaPadding = DEFAULT_PIP_MARGINS + + /** The distance the PiP peeks into the screen when stashed */ + var stashOffset = DEFAULT_PIP_MARGINS + + /** + * How long (in milliseconds) the PiP should stay stashed for after the last time the + * keep clear areas causing the PiP to stash have changed. + */ + var stashDuration = DEFAULT_STASH_DURATION + + /** The fraction of screen width/height restricted keep clear areas can move the PiP */ + var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION + + private var pipGravity = Gravity.BOTTOM or Gravity.RIGHT + private var transformedScreenBounds = Rect() + private var transformedMovementBounds = Rect() + + private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet() + private var lastStashTime: Long = Long.MIN_VALUE + + /** + * Calculates the position the PiP should be placed at, taking into consideration the + * given keep clear areas. + * + * Restricted keep clear areas can move the PiP only by a limited amount, and may be ignored + * if there is no space for the PiP to move to. + * Apps holding the permission [android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS] + * can declare unrestricted keep clear areas, which can move the PiP farther and placement will + * always try to respect these areas. + * + * If no free space the PiP is allowed to move to can be found, a stashed position is returned + * as [Placement.bounds], along with a position to move to once [Placement.unstashTime] has + * passed as [Placement.unstashDestinationBounds]. + * + * @param pipSize The size of the PiP window + * @param restrictedAreas The restricted keep clear areas + * @param unrestrictedAreas The unrestricted keep clear areas + * + */ + fun calculatePipPosition( + pipSize: Size, + restrictedAreas: Set<Rect>, + unrestrictedAreas: Set<Rect> + ): Placement { + val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas) + val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas) + val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds) + + val result = calculatePipPositionTransformed( + pipAnchorBounds, + transformedRestrictedAreas, + transformedUnrestrictedAreas + ) + + val screenSpaceBounds = fromTransformedSpace(result.bounds) + return Placement( + screenSpaceBounds, + fromTransformedSpace(result.anchorBounds), + getStashType(screenSpaceBounds, movementBounds), + result.unstashDestinationBounds?.let { fromTransformedSpace(it) }, + result.unstashTime + ) + } + + /** + * Filters out areas that encompass the entire movement bounds and returns them mapped to + * the base case space. + * + * Areas encompassing the entire movement bounds can occur when a full-screen View gets focused, + * but we don't want this to cause the PiP to get stashed. + */ + private fun transformAndFilterAreas(areas: Set<Rect>): Set<Rect> { + return areas.mapNotNullTo(mutableSetOf()) { + when { + it.contains(movementBounds) -> null + else -> toTransformedSpace(it) + } + } + } + + /** + * Calculates the position the PiP should be placed at, taking into consideration the + * given keep clear areas. + * All parameters are transformed from screen space to the base case space, where the PiP + * anchor is in the bottom right corner / on the right side. + * + * @see [calculatePipPosition] + */ + private fun calculatePipPositionTransformed( + pipAnchorBounds: Rect, + restrictedAreas: Set<Rect>, + unrestrictedAreas: Set<Rect> + ): Placement { + if (restrictedAreas.isEmpty() && unrestrictedAreas.isEmpty()) { + return Placement(pipAnchorBounds, pipAnchorBounds) + } + + // First try to find a free position to move to + val freeMovePos = findFreeMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas) + if (freeMovePos != null) { + lastAreasOverlappingUnstashPosition = emptySet() + return Placement(freeMovePos, pipAnchorBounds) + } + + // If no free position is found, we have to stash the PiP. + // Find the position the PiP should return to once it unstashes by doing a relaxed + // search, or ignoring restricted areas, or returning to the anchor position + val unstashBounds = + findRelaxedMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas) + ?: findFreeMovePosition(pipAnchorBounds, emptySet(), unrestrictedAreas) + ?: pipAnchorBounds + + val keepClearAreas = restrictedAreas + unrestrictedAreas + val areasOverlappingUnstashPosition = + keepClearAreas.filter { Rect.intersects(it, unstashBounds) }.toSet() + val areasOverlappingUnstashPositionChanged = + !lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition) + lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition + + val now = clock() + if (areasOverlappingUnstashPositionChanged) { + lastStashTime = now + } + + // If overlapping areas haven't changed and the stash duration has passed, we can + // place the PiP at the unstash position + val unstashTime = lastStashTime + stashDuration + if (now >= unstashTime) { + return Placement(unstashBounds, pipAnchorBounds) + } + + // Otherwise, we'll stash it close to the unstash position + val stashedBounds = getNearbyStashedPosition(unstashBounds, keepClearAreas) + return Placement( + stashedBounds, + pipAnchorBounds, + getStashType(stashedBounds, transformedMovementBounds), + unstashBounds, + unstashTime + ) + } + + @PipBoundsState.StashType + private fun getStashType(stashedBounds: Rect, movementBounds: Rect): Int { + return when { + stashedBounds.left < movementBounds.left -> STASH_TYPE_LEFT + stashedBounds.right > movementBounds.right -> STASH_TYPE_RIGHT + stashedBounds.top < movementBounds.top -> STASH_TYPE_TOP + stashedBounds.bottom > movementBounds.bottom -> STASH_TYPE_BOTTOM + else -> STASH_TYPE_NONE + } + } + + private fun findRelaxedMovePosition( + pipAnchorBounds: Rect, + restrictedAreas: Set<Rect>, + unrestrictedAreas: Set<Rect> + ): Rect? { + if (RELAX_DEPTH <= 0) { + // relaxed search disabled + return null + } + + return findRelaxedMovePosition( + RELAX_DEPTH, + pipAnchorBounds, + restrictedAreas.toMutableSet(), + unrestrictedAreas + ) + } + + private fun findRelaxedMovePosition( + depth: Int, + pipAnchorBounds: Rect, + restrictedAreas: MutableSet<Rect>, + unrestrictedAreas: Set<Rect> + ): Rect? { + if (depth == 0) { + return findFreeMovePosition(pipAnchorBounds, restrictedAreas, unrestrictedAreas) + } + + val candidates = mutableListOf<Rect>() + val areasToExclude = restrictedAreas.toList() + for (area in areasToExclude) { + restrictedAreas.remove(area) + val candidate = findRelaxedMovePosition( + depth - 1, + pipAnchorBounds, + restrictedAreas, + unrestrictedAreas + ) + restrictedAreas.add(area) + + if (candidate != null) { + candidates.add(candidate) + } + } + return candidates.minByOrNull { candidateCost(it, pipAnchorBounds) } + } + + /** Cost function to evaluate candidate bounds */ + private fun candidateCost(candidateBounds: Rect, pipAnchorBounds: Rect): Int { + // squared euclidean distance of corresponding rect corners + val dx = candidateBounds.left - pipAnchorBounds.left + val dy = candidateBounds.top - pipAnchorBounds.top + return dx * dx + dy * dy + } + + private fun findFreeMovePosition( + pipAnchorBounds: Rect, + restrictedAreas: Set<Rect>, + unrestrictedAreas: Set<Rect> + ): Rect? { + val movementBounds = transformedMovementBounds + val candidateEdgeRects = mutableListOf<Rect>() + val minRestrictedLeft = + pipAnchorBounds.right - screenSize.width * maxRestrictedDistanceFraction + + candidateEdgeRects.add( + movementBounds.offsetCopy(movementBounds.width() + pipAreaPadding, 0) + ) + candidateEdgeRects.addAll(unrestrictedAreas) + candidateEdgeRects.addAll(restrictedAreas.filter { it.left >= minRestrictedLeft }) + + // throw out edges that are too close to the left screen edge to fit the PiP + val minLeft = movementBounds.left + pipAnchorBounds.width() + candidateEdgeRects.retainAll { it.left - pipAreaPadding > minLeft } + candidateEdgeRects.sortBy { -it.left } + + val maxRestrictedDY = (screenSize.height * maxRestrictedDistanceFraction).roundToInt() + + val candidateBounds = mutableListOf<Rect>() + for (edgeRect in candidateEdgeRects) { + val edge = edgeRect.left - pipAreaPadding + val dx = (edge - pipAnchorBounds.width()) - pipAnchorBounds.left + val candidatePipBounds = pipAnchorBounds.offsetCopy(dx, 0) + val searchUp = true + val searchDown = !isPipAnchoredToCorner() + + if (searchUp) { + val event = findMinMoveUp(candidatePipBounds, restrictedAreas, unrestrictedAreas) + val padding = if (event.start) 0 else pipAreaPadding + val dy = event.pos - pipAnchorBounds.bottom - padding + val maxDY = if (event.unrestricted) movementBounds.height() else maxRestrictedDY + val candidate = pipAnchorBounds.offsetCopy(dx, dy) + val isOnScreen = candidate.top > movementBounds.top + val hangingMidAir = !candidate.intersectsY(edgeRect) + if (isOnScreen && abs(dy) <= maxDY && !hangingMidAir) { + candidateBounds.add(candidate) + } + } + + if (searchDown) { + val event = findMinMoveDown(candidatePipBounds, restrictedAreas, unrestrictedAreas) + val padding = if (event.start) 0 else pipAreaPadding + val dy = event.pos - pipAnchorBounds.top + padding + val maxDY = if (event.unrestricted) movementBounds.height() else maxRestrictedDY + val candidate = pipAnchorBounds.offsetCopy(dx, dy) + val isOnScreen = candidate.bottom < movementBounds.bottom + val hangingMidAir = !candidate.intersectsY(edgeRect) + if (isOnScreen && abs(dy) <= maxDY && !hangingMidAir) { + candidateBounds.add(candidate) + } + } + } + + candidateBounds.sortBy { candidateCost(it, pipAnchorBounds) } + return candidateBounds.firstOrNull() + } + + private fun getNearbyStashedPosition(bounds: Rect, keepClearAreas: Set<Rect>): Rect { + val screenBounds = transformedScreenBounds + val stashCandidates = Array(2) { Rect(bounds) } + val areasOverlappingPipX = keepClearAreas.filter { it.intersectsX(bounds) } + val areasOverlappingPipY = keepClearAreas.filter { it.intersectsY(bounds) } + + if (screenBounds.bottom - bounds.bottom <= bounds.top - screenBounds.top) { + // bottom is closer than top, stash downwards + val fullStashTop = screenBounds.bottom - stashOffset + + val maxBottom = areasOverlappingPipX.maxByOrNull { it.bottom }!!.bottom + val partialStashTop = maxBottom + pipAreaPadding + + val downPosition = stashCandidates[0] + downPosition.offsetTo(bounds.left, min(fullStashTop, partialStashTop)) + } else { + // top is closer than bottom, stash upwards + val fullStashY = screenBounds.top - bounds.height() + stashOffset + + val minTop = areasOverlappingPipX.minByOrNull { it.top }!!.top + val partialStashY = minTop - bounds.height() - pipAreaPadding + + val upPosition = stashCandidates[0] + upPosition.offsetTo(bounds.left, max(fullStashY, partialStashY)) + } + + if (screenBounds.right - bounds.right <= bounds.left - screenBounds.left) { + // right is closer than left, stash rightwards + val fullStashLeft = screenBounds.right - stashOffset + + val maxRight = areasOverlappingPipY.maxByOrNull { it.right }!!.right + val partialStashLeft = maxRight + pipAreaPadding + + val rightPosition = stashCandidates[1] + rightPosition.offsetTo(min(fullStashLeft, partialStashLeft), bounds.top) + } else { + // left is closer than right, stash leftwards + val fullStashLeft = screenBounds.left - bounds.width() + stashOffset + + val minLeft = areasOverlappingPipY.minByOrNull { it.left }!!.left + val partialStashLeft = minLeft - bounds.width() - pipAreaPadding + + val rightPosition = stashCandidates[1] + rightPosition.offsetTo(max(fullStashLeft, partialStashLeft), bounds.top) + } + + return stashCandidates.minByOrNull { + val dx = abs(it.left - bounds.left) + val dy = abs(it.top - bounds.top) + dx * bounds.height() + dy * bounds.width() + }!! + } + + /** + * Prevents the PiP from being stashed for the current set of keep clear areas. + * The PiP may stash again if keep clear areas change. + */ + fun keepUnstashedForCurrentKeepClearAreas() { + lastStashTime = Long.MIN_VALUE + } + + /** + * Updates the size of the screen. + * + * @param size The new size of the screen + */ + fun setScreenSize(size: Size) { + if (screenSize == size) { + return + } + + screenSize = size + transformedScreenBounds = + toTransformedSpace(Rect(0, 0, screenSize.width, screenSize.height)) + transformedMovementBounds = toTransformedSpace(transformedMovementBounds) + } + + /** + * Updates the bounds within which the PiP is allowed to move. + * + * @param bounds The new movement bounds + */ + fun setMovementBounds(bounds: Rect) { + if (movementBounds == bounds) { + return + } + + movementBounds.set(bounds) + transformedMovementBounds = toTransformedSpace(movementBounds) + } + + /** + * Sets the corner/side of the PiP's home position. + */ + fun setGravity(gravity: Int) { + if (pipGravity == gravity) return + + pipGravity = gravity + transformedScreenBounds = + toTransformedSpace(Rect(0, 0, screenSize.width, screenSize.height)) + transformedMovementBounds = toTransformedSpace(movementBounds) + } + + /** + * @param open Whether this event marks the opening of an occupied segment + * @param pos The coordinate of this event + * @param unrestricted Whether this event was generated by an unrestricted keep clear area + * @param start Marks the special start event. Earlier events are skipped when sweeping + */ + data class SweepLineEvent( + val open: Boolean, + val pos: Int, + val unrestricted: Boolean, + val start: Boolean = false + ) + + /** + * Returns a [SweepLineEvent] representing the minimal move up from [pipBounds] that clears + * the given keep clear areas. + */ + private fun findMinMoveUp( + pipBounds: Rect, + restrictedAreas: Set<Rect>, + unrestrictedAreas: Set<Rect> + ): SweepLineEvent { + val events = mutableListOf<SweepLineEvent>() + val generateEvents: (Boolean) -> (Rect) -> Unit = { unrestricted -> + { area -> + if (pipBounds.intersectsX(area)) { + events.add(SweepLineEvent(true, area.bottom, unrestricted)) + events.add(SweepLineEvent(false, area.top, unrestricted)) + } + } + } + + restrictedAreas.forEach(generateEvents(false)) + unrestrictedAreas.forEach(generateEvents(true)) + + return sweepLineFindEarliestGap( + events, + pipBounds.height() + pipAreaPadding, + pipBounds.bottom, + pipBounds.height() + ) + } + + /** + * Returns a [SweepLineEvent] representing the minimal move down from [pipBounds] that clears + * the given keep clear areas. + */ + private fun findMinMoveDown( + pipBounds: Rect, + restrictedAreas: Set<Rect>, + unrestrictedAreas: Set<Rect> + ): SweepLineEvent { + val events = mutableListOf<SweepLineEvent>() + val generateEvents: (Boolean) -> (Rect) -> Unit = { unrestricted -> + { area -> + if (pipBounds.intersectsX(area)) { + events.add(SweepLineEvent(true, -area.top, unrestricted)) + events.add(SweepLineEvent(false, -area.bottom, unrestricted)) + } + } + } + + restrictedAreas.forEach(generateEvents(false)) + unrestrictedAreas.forEach(generateEvents(true)) + + val earliestEvent = sweepLineFindEarliestGap( + events, + pipBounds.height() + pipAreaPadding, + -pipBounds.top, + pipBounds.height() + ) + + return earliestEvent.copy(pos = -earliestEvent.pos) + } + + /** + * Takes a list of events representing the starts & ends of occupied segments, and + * returns the earliest event whose position is unoccupied and has [gapSize] distance to the + * next event. + * + * @param events List of [SweepLineEvent] representing occupied segments + * @param gapSize Size of the gap to search for + * @param startPos The position to start the search on. + * Inserts a special event marked with [SweepLineEvent.start]. + * @param startGapSize Used instead of [gapSize] for the start event + */ + private fun sweepLineFindEarliestGap( + events: MutableList<SweepLineEvent>, + gapSize: Int, + startPos: Int, + startGapSize: Int + ): SweepLineEvent { + events.add( + SweepLineEvent( + open = false, + pos = startPos, + unrestricted = true, + start = true + ) + ) + events.sortBy { -it.pos } + + // sweep + var openCount = 0 + var i = 0 + while (i < events.size) { + val event = events[i] + if (!event.start) { + if (event.open) { + openCount++ + } else { + openCount-- + } + } + + if (openCount == 0) { + // check if placement is possible + val candidate = event.pos + if (candidate > startPos) { + i++ + continue + } + + val eventGapSize = if (event.start) startGapSize else gapSize + val nextEvent = events.getOrNull(i + 1) + if (nextEvent == null || nextEvent.pos < candidate - eventGapSize) { + return event + } + } + i++ + } + + return events.last() + } + + private fun shouldTransformFlipX(): Boolean { + return when (pipGravity) { + (Gravity.TOP), (Gravity.TOP or Gravity.CENTER_HORIZONTAL) -> true + (Gravity.TOP or Gravity.LEFT) -> true + (Gravity.LEFT), (Gravity.LEFT or Gravity.CENTER_VERTICAL) -> true + (Gravity.BOTTOM or Gravity.LEFT) -> true + else -> false + } + } + + private fun shouldTransformFlipY(): Boolean { + return when (pipGravity) { + (Gravity.TOP or Gravity.LEFT) -> true + (Gravity.TOP or Gravity.RIGHT) -> true + else -> false + } + } + + private fun shouldTransformRotate(): Boolean { + val horizontalGravity = pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK + val leftOrRight = horizontalGravity == Gravity.LEFT || horizontalGravity == Gravity.RIGHT + + if (leftOrRight) return false + return when (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) { + (Gravity.TOP) -> true + (Gravity.BOTTOM) -> true + else -> false + } + } + + /** + * Transforms the given rect from screen space into the base case space, where the PiP + * anchor is positioned in the bottom right corner or on the right side (for expanded PiP). + * + * @see [fromTransformedSpace] + */ + private fun toTransformedSpace(r: Rect): Rect { + var screenWidth = screenSize.width + var screenHeight = screenSize.height + + val tl = Point(r.left, r.top) + val tr = Point(r.right, r.top) + val br = Point(r.right, r.bottom) + val bl = Point(r.left, r.bottom) + val corners = arrayOf(tl, tr, br, bl) + + // rotate first (CW) + if (shouldTransformRotate()) { + corners.forEach { p -> + val px = p.x + val py = p.y + p.x = py + p.y = -px + p.y += screenWidth // shift back screen into positive quadrant + } + screenWidth = screenSize.height + screenHeight = screenSize.width + } + + // flip second + corners.forEach { + if (shouldTransformFlipX()) it.x = screenWidth - it.x + if (shouldTransformFlipY()) it.y = screenHeight - it.y + } + + val top = corners.minByOrNull { it.y }!!.y + val right = corners.maxByOrNull { it.x }!!.x + val bottom = corners.maxByOrNull { it.y }!!.y + val left = corners.minByOrNull { it.x }!!.x + + return Rect(left, top, right, bottom) + } + + /** + * Transforms the given rect from the base case space, where the PiP anchor is positioned in + * the bottom right corner or on the right side, back into screen space. + * + * @see [toTransformedSpace] + */ + private fun fromTransformedSpace(r: Rect): Rect { + val rotate = shouldTransformRotate() + val transformedScreenWidth = if (rotate) screenSize.height else screenSize.width + val transformedScreenHeight = if (rotate) screenSize.width else screenSize.height + + val tl = Point(r.left, r.top) + val tr = Point(r.right, r.top) + val br = Point(r.right, r.bottom) + val bl = Point(r.left, r.bottom) + val corners = arrayOf(tl, tr, br, bl) + + // flip first + corners.forEach { + if (shouldTransformFlipX()) it.x = transformedScreenWidth - it.x + if (shouldTransformFlipY()) it.y = transformedScreenHeight - it.y + } + + // rotate second (CCW) + if (rotate) { + corners.forEach { p -> + p.y -= screenSize.width // undo shift back screen into positive quadrant + val px = p.x + val py = p.y + p.x = -py + p.y = px + } + } + + val top = corners.minByOrNull { it.y }!!.y + val right = corners.maxByOrNull { it.x }!!.x + val bottom = corners.maxByOrNull { it.y }!!.y + val left = corners.minByOrNull { it.x }!!.x + + return Rect(left, top, right, bottom) + } + + /** PiP anchor bounds in base case for given gravity */ + private fun getNormalPipAnchorBounds(pipSize: Size, movementBounds: Rect): Rect { + var size = pipSize + val rotateCW = shouldTransformRotate() + if (rotateCW) { + size = Size(pipSize.height, pipSize.width) + } + + val pipBounds = Rect() + if (isPipAnchoredToCorner()) { + // bottom right + Gravity.apply( + Gravity.BOTTOM or Gravity.RIGHT, + size.width, + size.height, + movementBounds, + pipBounds + ) + return pipBounds + } else { + // expanded, right side + Gravity.apply(Gravity.RIGHT, size.width, size.height, movementBounds, pipBounds) + return pipBounds + } + } + + private fun isPipAnchoredToCorner(): Boolean { + val left = (pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT + val right = (pipGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.RIGHT + val top = (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP + val bottom = (pipGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM + + val horizontal = left || right + val vertical = top || bottom + + return horizontal && vertical + } + + private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) } + private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom + private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 32ebe2d6aecf..b3c230683cb3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -174,8 +174,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } void updateExpansionState() { - mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipEnabled() - && mTvPipBoundsState.getTvExpandedAspectRatio() != 0); + mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported() + && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0); mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded()); } @@ -202,12 +202,17 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } } + boolean isInMoveMode() { + return mInMoveMode; + } + @Override public void onEnterMoveMode() { if (DEBUG) Log.d(TAG, "onEnterMoveMode - " + mInMoveMode); mInMoveMode = true; mPipMenuView.showMenuButtons(false); mPipMenuView.showMovementHints(mDelegate.getPipGravity()); + mDelegate.onInMoveModeChanged(); } @Override @@ -217,6 +222,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mInMoveMode = false; mPipMenuView.showMenuButtons(true); mPipMenuView.hideMovementHints(); + mDelegate.onInMoveModeChanged(); return true; } return false; @@ -447,6 +453,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis void movePip(int keycode); + void onInMoveModeChanged(); + int getPipGravity(); void togglePipExpansion(); 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 1a5e3f26ab37..9e5359332055 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 @@ -1117,9 +1117,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - if (token.equals(mMainStage.mRootTaskInfo.getToken())) { + if (mMainStage.containsToken(token)) { return getMainStagePosition(); - } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) { + } else if (mSideStage.containsToken(token)) { return getSideStagePosition(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 109f96e166d7..5f0cd01f5416 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -124,6 +124,20 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { return mChildrenTaskInfo.contains(taskId); } + boolean containsToken(WindowContainerToken token) { + if (token.equals(mRootTaskInfo.token)) { + return true; + } + + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { + if (token.equals(mChildrenTaskInfo.valueAt(i).token)) { + return true; + } + } + + return false; + } + /** * Returns the top visible child task's id. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 610d2cc39445..fb3cd87448d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -73,9 +73,9 @@ public class Transitions implements RemoteCallable<Transitions> { /** Set to {@code true} to enable shell transitions. */ public static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.debug.shell_transit", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS - && SystemProperties.getBoolean("persist.debug.shell_transit_rotate", false); + && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); /** Transition type for exiting PIP via the Shell, via pressing the expand button. */ public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 1; diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt index 57bc0d580d72..3dd9e0572947 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt @@ -61,7 +61,7 @@ abstract class BaseAppHelper( private const val APP_CLOSE_WAIT_TIME_MS = 3_000L fun isShellTransitionsEnabled() = - SystemProperties.getBoolean("persist.debug.shell_transit", false) + SystemProperties.getBoolean("persist.wm.debug.shell_transit", false) fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { try { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java index 46fe201072c9..4523e2c9cba5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java @@ -50,7 +50,7 @@ import java.util.Optional; @SmallTest public class FullscreenTaskListenerTest { private static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.debug.shell_transit", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); @Mock private SyncTransactionQueue mSyncQueue; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java new file mode 100644 index 000000000000..78903dca7657 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.kidsmode; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.DEFAULT_DISPLAY; + +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.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.window.ITaskOrganizerController; +import android.window.TaskAppearedInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.policy.ForceShowNavigationBarSettingsObserver; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.startingsurface.StartingWindowController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Optional; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class KidsModeTaskOrganizerTest { + @Mock private ITaskOrganizerController mTaskOrganizerController; + @Mock private Context mContext; + @Mock private Handler mHandler; + @Mock private CompatUIController mCompatUI; + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock private ShellExecutor mTestExecutor; + @Mock private DisplayController mDisplayController; + @Mock private SurfaceControl mLeash; + @Mock private WindowContainerToken mToken; + @Mock private WindowContainerTransaction mTransaction; + @Mock private ForceShowNavigationBarSettingsObserver mObserver; + @Mock private StartingWindowController mStartingWindowController; + @Mock private DisplayInsetsController mDisplayInsetsController; + + KidsModeTaskOrganizer mOrganizer; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + try { + doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList()) + .when(mTaskOrganizerController).registerTaskOrganizer(any()); + } catch (RemoteException e) { } + mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor, + mHandler, mContext, mCompatUI, mSyncTransactionQueue, mDisplayController, + mDisplayInsetsController, Optional.empty(), mObserver)); + mOrganizer.initialize(mStartingWindowController); + doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction(); + doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY); + } + + @Test + public void testKidsModeOn() { + doReturn(true).when(mObserver).isEnabled(); + + mOrganizer.updateKidsModeState(); + + verify(mOrganizer, times(1)).enable(); + verify(mOrganizer, times(1)).registerOrganizer(); + verify(mOrganizer, times(1)).createRootTask( + eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie)); + + final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12, + WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie); + mOrganizer.onTaskAppeared(rootTask, mLeash); + + assertThat(mOrganizer.mLaunchRootLeash).isEqualTo(mLeash); + assertThat(mOrganizer.mLaunchRootTask).isEqualTo(rootTask); + } + + @Test + public void testKidsModeOff() { + doReturn(true).when(mObserver).isEnabled(); + mOrganizer.updateKidsModeState(); + final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12, + WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie); + mOrganizer.onTaskAppeared(rootTask, mLeash); + + doReturn(false).when(mObserver).isEnabled(); + mOrganizer.updateKidsModeState(); + + + verify(mOrganizer, times(1)).disable(); + verify(mOrganizer, times(1)).unregisterOrganizer(); + verify(mOrganizer, times(1)).deleteRootTask(rootTask.token); + assertThat(mOrganizer.mLaunchRootLeash).isNull(); + assertThat(mOrganizer.mLaunchRootTask).isNull(); + } + + private ActivityManager.RunningTaskInfo createTaskInfo( + int taskId, int windowingMode, IBinder cookies) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.token = mToken; + taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); + final ArrayList<IBinder> launchCookies = new ArrayList<>(); + if (cookies != null) { + launchCookies.add(cookies); + } + taskInfo.launchCookies = launchCookies; + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt new file mode 100644 index 000000000000..e6ba70e1b60e --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip.tv + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.util.Size +import android.view.Gravity +import org.junit.runner.RunWith +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement +import org.junit.Before +import org.junit.Test +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertNull + +@RunWith(AndroidTestingRunner::class) +class TvPipKeepClearAlgorithmTest { + private val DEFAULT_PIP_SIZE = Size(384, 216) + private val EXPANDED_WIDE_PIP_SIZE = Size(384*2, 216) + private val DASHBOARD_WIDTH = 484 + private val BOTTOM_SHEET_HEIGHT = 524 + private val STASH_OFFSET = 64 + private val PADDING = 16 + private val SCREEN_SIZE = Size(1920, 1080) + private val SCREEN_EDGE_INSET = 50 + + private lateinit var pipSize: Size + private lateinit var movementBounds: Rect + private lateinit var algorithm: TvPipKeepClearAlgorithm + private var currentTime = 0L + private var restrictedAreas = mutableSetOf<Rect>() + private var unrestrictedAreas = mutableSetOf<Rect>() + private var gravity: Int = 0 + + @Before + fun setup() { + movementBounds = Rect(0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height) + movementBounds.inset(SCREEN_EDGE_INSET, SCREEN_EDGE_INSET) + + restrictedAreas.clear() + unrestrictedAreas.clear() + currentTime = 0L + pipSize = DEFAULT_PIP_SIZE + gravity = Gravity.BOTTOM or Gravity.RIGHT + + algorithm = TvPipKeepClearAlgorithm({ currentTime }) + algorithm.setScreenSize(SCREEN_SIZE) + algorithm.setMovementBounds(movementBounds) + algorithm.pipAreaPadding = PADDING + algorithm.stashOffset = STASH_OFFSET + algorithm.stashDuration = 5000L + algorithm.setGravity(gravity) + algorithm.maxRestrictedDistanceFraction = 0.3 + } + + @Test + fun testAnchorPosition_BottomRight() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + testAnchorPosition() + } + + @Test + fun testAnchorPosition_TopRight() { + gravity = Gravity.TOP or Gravity.RIGHT + testAnchorPosition() + } + + @Test + fun testAnchorPosition_TopLeft() { + gravity = Gravity.TOP or Gravity.LEFT + testAnchorPosition() + } + + @Test + fun testAnchorPosition_BottomLeft() { + gravity = Gravity.BOTTOM or Gravity.LEFT + testAnchorPosition() + } + + @Test + fun testAnchorPosition_Right() { + gravity = Gravity.RIGHT + testAnchorPosition() + } + + @Test + fun testAnchorPosition_Left() { + gravity = Gravity.LEFT + testAnchorPosition() + } + + @Test + fun testAnchorPosition_Top() { + gravity = Gravity.TOP + testAnchorPosition() + } + + @Test + fun testAnchorPosition_Bottom() { + gravity = Gravity.BOTTOM + testAnchorPosition() + } + + @Test + fun testAnchorPosition_TopCenterHorizontal() { + gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL + testAnchorPosition() + } + + @Test + fun testAnchorPosition_BottomCenterHorizontal() { + gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + testAnchorPosition() + } + + @Test + fun testAnchorPosition_RightCenterVertical() { + gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL + testAnchorPosition() + } + + @Test + fun testAnchorPosition_LeftCenterVertical() { + gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL + testAnchorPosition() + } + + fun testAnchorPosition() { + val placement = getActualPlacement() + + assertEquals(getExpectedAnchorBounds(), placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorBottomRight_KeepClearNotObstructing_StayAtAnchor() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.LEFT) + unrestrictedAreas.add(sidebar) + + val expectedBounds = getExpectedAnchorBounds() + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorBottomRight_UnrestrictedRightSidebar_PushedLeft() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) + unrestrictedAreas.add(sidebar) + + val expectedBounds = anchorBoundsOffsetBy(SCREEN_EDGE_INSET - sidebar.width() - PADDING, 0) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorTopRight_UnrestrictedRightSidebar_PushedLeft() { + gravity = Gravity.TOP or Gravity.RIGHT + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) + unrestrictedAreas.add(sidebar) + + val expectedBounds = anchorBoundsOffsetBy(SCREEN_EDGE_INSET - sidebar.width() - PADDING, 0) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorBottomLeft_UnrestrictedRightSidebar_StayAtAnchor() { + gravity = Gravity.BOTTOM or Gravity.LEFT + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) + unrestrictedAreas.add(sidebar) + + val expectedBounds = anchorBoundsOffsetBy(0, 0) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() { + gravity = Gravity.BOTTOM + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) + unrestrictedAreas.add(sidebar) + + val expectedBounds = anchorBoundsOffsetBy(0, 0) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun testExpanded_AnchorBottom_UnrestrictedRightSidebar_StayAtAnchor() { + pipSize = EXPANDED_WIDE_PIP_SIZE + gravity = Gravity.BOTTOM + + val sidebar = makeSideBar(DASHBOARD_WIDTH, Gravity.RIGHT) + unrestrictedAreas.add(sidebar) + + val expectedBounds = anchorBoundsOffsetBy(0, 0) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorBottomRight_RestrictedSmallBottomBar_PushedUp() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(96) + restrictedAreas.add(bottomBar) + + val expectedBounds = anchorBoundsOffsetBy(0, + SCREEN_EDGE_INSET - bottomBar.height() - PADDING) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorBottomRight_RestrictedBottomSheet_StashDownAtAnchor() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) + restrictedAreas.add(bottomBar) + + val expectedBounds = getExpectedAnchorBounds() + expectedBounds.offsetTo(expectedBounds.left, SCREEN_SIZE.height - STASH_OFFSET) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_BOTTOM, placement.stashType) + assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds) + assertEquals(algorithm.stashDuration, placement.unstashTime) + } + + @Test + fun test_AnchorBottomRight_UnrestrictedBottomSheet_PushUp() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) + unrestrictedAreas.add(bottomBar) + + val expectedBounds = anchorBoundsOffsetBy(0, + SCREEN_EDGE_INSET - bottomBar.height() - PADDING) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_AnchorBottomRight_UnrestrictedBottomSheet_RestrictedSidebar_StashAboveBottomSheet() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) + unrestrictedAreas.add(bottomBar) + + val maxRestrictedHorizontalPush = + (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt() + val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT) + restrictedAreas.add(sideBar) + + val expectedUnstashBounds = + anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING) + + val expectedBounds = Rect(expectedUnstashBounds) + expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) + assertEquals(algorithm.stashDuration, placement.unstashTime) + } + + @Test + fun test_AnchorBottomRight_UnrestrictedBottomSheet_UnrestrictedSidebar_PushUpLeft() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) + unrestrictedAreas.add(bottomBar) + + val maxRestrictedHorizontalPush = + (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt() + val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT) + unrestrictedAreas.add(sideBar) + + val expectedBounds = anchorBoundsOffsetBy( + SCREEN_EDGE_INSET - sideBar.width() - PADDING, + SCREEN_EDGE_INSET - bottomBar.height() - PADDING + ) + + val placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_Stashed_UnstashBoundsBecomeUnobstructed_Unstashes() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) + unrestrictedAreas.add(bottomBar) + + val maxRestrictedHorizontalPush = + (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt() + val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT) + restrictedAreas.add(sideBar) + + val expectedUnstashBounds = + anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING) + + val expectedBounds = Rect(expectedUnstashBounds) + expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top) + + var placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) + assertEquals(algorithm.stashDuration, placement.unstashTime) + + currentTime += 1000 + + restrictedAreas.remove(sideBar) + placement = getActualPlacement() + assertEquals(expectedUnstashBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_Stashed_UnstashBoundsStaysObstructed_UnstashesAfterTimeout() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) + unrestrictedAreas.add(bottomBar) + + val maxRestrictedHorizontalPush = + (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt() + val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT) + restrictedAreas.add(sideBar) + + val expectedUnstashBounds = + anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING) + + val expectedBounds = Rect(expectedUnstashBounds) + expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top) + + var placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) + assertEquals(algorithm.stashDuration, placement.unstashTime) + + currentTime += algorithm.stashDuration + + placement = getActualPlacement() + assertEquals(expectedUnstashBounds, placement.bounds) + assertNotStashed(placement) + } + + @Test + fun test_Stashed_UnstashBoundsObstructionChanges_UnstashTimeExtended() { + gravity = Gravity.BOTTOM or Gravity.RIGHT + + val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) + unrestrictedAreas.add(bottomBar) + + val maxRestrictedHorizontalPush = + (algorithm.maxRestrictedDistanceFraction * SCREEN_SIZE.width).toInt() + val sideBar = makeSideBar(maxRestrictedHorizontalPush + 100, Gravity.RIGHT) + restrictedAreas.add(sideBar) + + val expectedUnstashBounds = + anchorBoundsOffsetBy(0, SCREEN_EDGE_INSET - bottomBar.height() - PADDING) + + val expectedBounds = Rect(expectedUnstashBounds) + expectedBounds.offsetTo(SCREEN_SIZE.width - STASH_OFFSET, expectedBounds.top) + + var placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) + assertEquals(algorithm.stashDuration, placement.unstashTime) + + currentTime += 1000 + + val newObstruction = Rect( + 0, + expectedUnstashBounds.top, + expectedUnstashBounds.right, + expectedUnstashBounds.bottom + ) + restrictedAreas.add(newObstruction) + + placement = getActualPlacement() + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) + assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime) + } + + private fun makeSideBar(width: Int, @Gravity.GravityFlags side: Int): Rect { + val sidebar = Rect(0, 0, width, SCREEN_SIZE.height) + if (side == Gravity.RIGHT) { + sidebar.offsetTo(SCREEN_SIZE.width - width, 0) + } + return sidebar + } + + private fun makeBottomBar(height: Int): Rect { + return Rect(0, SCREEN_SIZE.height - height, SCREEN_SIZE.width, SCREEN_SIZE.height) + } + + private fun getExpectedAnchorBounds(): Rect { + val expectedBounds = Rect() + Gravity.apply(gravity, pipSize.width, pipSize.height, movementBounds, expectedBounds) + return expectedBounds + } + + private fun anchorBoundsOffsetBy(dx: Int, dy: Int): Rect { + val bounds = getExpectedAnchorBounds() + bounds.offset(dx, dy) + return bounds + } + + private fun getActualPlacement(): Placement { + algorithm.setGravity(gravity) + return algorithm.calculatePipPosition(pipSize, restrictedAreas, unrestrictedAreas) + } + + private fun assertNotStashed(actual: Placement) { + assertEquals(STASH_TYPE_NONE, actual.stashType) + assertNull(actual.unstashDestinationBounds) + assertEquals(0L, actual.unstashTime) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 13b726efb046..157c30bcb6c7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -62,7 +62,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public final class StageTaskListenerTests extends ShellTestCase { private static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.debug.shell_transit", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); @Mock private ShellTaskOrganizer mTaskOrganizer; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 432196c9e4ef..1a56b1542b07 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6952,7 +6952,8 @@ public class AudioManager { for (Integer format : formatsList) { int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format); if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - codecConfigList.add(new BluetoothCodecConfig(btSourceCodec)); + codecConfigList.add( + new BluetoothCodecConfig.Builder().setCodecType(btSourceCodec).build()); } } return codecConfigList; diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 47e402f73f40..d8995b419f50 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -1118,6 +1118,11 @@ public final class MediaRouter2 { private List<MediaRoute2Info> filterRoutesWithIndividualPreference( List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryPreference) { List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); + if (isSystemRouter()) { + // Individual discovery preferences do not apply for the system router. + filteredRoutes.addAll(routes); + return filteredRoutes; + } for (MediaRoute2Info route : routes) { if (!route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) { continue; diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java index e1f535c93d19..6103db001b19 100644 --- a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java @@ -131,6 +131,10 @@ public final class TvInteractiveAppInfo implements Parcelable { dest.writeInt(mTypes); } + /** + * Returns a unique ID for this TV interactive app service. The ID is generated from the package + * and class name implementing the TV interactive app service. + */ @NonNull public String getId() { return mId; diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index f75aa56813ab..749b5cd57104 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -1813,8 +1813,8 @@ public final class TvInteractiveAppManager { } /** - * This is called when {@link TvIAppService.Session#notifyTeletextAppStateChanged} is - * called. + * This is called when {@link TvInteractiveAppService.Session#notifyTeletextAppStateChanged} + * is called. * * @param session A {@link TvInteractiveAppManager.Session} associated with this callback. * @param state the current state. diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 52b00b7b43e5..43ed30779ca0 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -19,6 +19,7 @@ package android.media.tv.interactive; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; import android.annotation.StringDef; import android.annotation.SuppressLint; import android.app.ActivityManager; @@ -76,15 +77,14 @@ public abstract class TvInteractiveAppService extends Service { private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000; - // TODO: cleanup and unhide APIs. - /** * This is the interface name that a service implementing a TV Interactive App service should * say that it supports -- that is, this is the action it uses for its intent filter. To be * supported, the service must also require the - * android.Manifest.permission#BIND_TV_INTERACTIVE_APP permission so that other applications - * cannot abuse it. + * {@link android.Manifest.permission#BIND_TV_INTERACTIVE_APP} permission so that other + * applications cannot abuse it. */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvInteractiveAppService"; @@ -383,7 +383,7 @@ public abstract class TvInteractiveAppService extends Service { } /** - * Resets TvIAppService session. + * Resets TvInteractiveAppService session. */ public void onResetInteractiveApp() { } diff --git a/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml new file mode 100644 index 000000000000..125fee6282d5 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@android:color/transparent" /> + <corners android:topLeftRadius="16dp" android:topRightRadius="16dp" + android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/> + <stroke + android:width="2dp" + android:color="@android:color/system_accent1_600" /> +</shape>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/btn_negative_top.xml b/packages/CompanionDeviceManager/res/drawable/btn_negative_top.xml new file mode 100644 index 000000000000..7df92bb145cb --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/btn_negative_top.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@android:color/system_accent1_100"/> + <corners android:topLeftRadius="12dp" android:topRightRadius="12dp" + android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp"/> +</shape> diff --git a/packages/CompanionDeviceManager/res/drawable/btn_positive_bottom.xml b/packages/CompanionDeviceManager/res/drawable/btn_positive_bottom.xml new file mode 100644 index 000000000000..55e96f6d7512 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/btn_positive_bottom.xml @@ -0,0 +1,22 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="@android:color/system_accent1_100"/> + <corners android:topLeftRadius="4dp" android:topRightRadius="4dp" + android:bottomLeftRadius="12dp" android:bottomRightRadius="12dp"/> +</shape> diff --git a/packages/CompanionDeviceManager/res/drawable/ic_apps.xml b/packages/CompanionDeviceManager/res/drawable/ic_apps.xml new file mode 100644 index 000000000000..93a0cba769c6 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/ic_apps.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M6.2529,18.5H16.2529V17.5H18.2529V21.5C18.2529,22.6 17.3529,23.5 16.2529,23.5H6.2529C5.1529,23.5 4.2529,22.6 4.2529,21.5V3.5C4.2529,2.4 5.1529,1.51 6.2529,1.51L16.2529,1.5C17.3529,1.5 18.2529,2.4 18.2529,3.5V7.5H16.2529V6.5H6.2529V18.5ZM16.2529,3.5H6.2529V4.5H16.2529V3.5ZM6.2529,21.5V20.5H16.2529V21.5H6.2529ZM12.6553,9.4049C12.6553,8.8526 13.103,8.4049 13.6553,8.4049H20.5254C21.0776,8.4049 21.5254,8.8526 21.5254,9.4049V14.6055C21.5254,15.1578 21.0776,15.6055 20.5254,15.6055H14.355L12.6553,17.0871V9.4049Z" + android:fillColor="#3C4043" + android:fillType="evenOdd"/> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml b/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml new file mode 100644 index 000000000000..4ac4d04b184e --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/ic_notifications.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/white" + android:pathData="M4,19V17H6V10Q6,7.925 7.25,6.312Q8.5,4.7 10.5,4.2V3.5Q10.5,2.875 10.938,2.438Q11.375,2 12,2Q12.625,2 13.062,2.438Q13.5,2.875 13.5,3.5V4.2Q15.5,4.7 16.75,6.312Q18,7.925 18,10V17H20V19ZM12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5Q12,11.5 12,11.5ZM12,22Q11.175,22 10.588,21.413Q10,20.825 10,20H14Q14,20.825 13.413,21.413Q12.825,22 12,22ZM8,17H16V10Q16,8.35 14.825,7.175Q13.65,6 12,6Q10.35,6 9.175,7.175Q8,8.35 8,10Z"/> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/ic_storage.xml b/packages/CompanionDeviceManager/res/drawable/ic_storage.xml new file mode 100644 index 000000000000..d8b7f59185c8 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/ic_storage.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/white" + android:pathData="M6,17H18L14.25,12L11.25,16L9,13ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19ZM5,5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Q5,19 5,19Q5,19 5,19V5Q5,5 5,5Q5,5 5,5Z"/> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml index f30dadffa788..9e5b1663ff45 100644 --- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml @@ -23,27 +23,27 @@ <!-- 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" - android:layout_height="wrap_content" - android:gravity="center" - android:paddingHorizontal="12dp" - style="@*android:style/TextAppearance.Widget.Toolbar.Title"/> + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingHorizontal="12dp" + style="@*android:style/TextAppearance.Widget.Toolbar.Title"/> <TextView - android:id="@+id/summary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:gravity="center" - android:textColor="?android:attr/textColorSecondary" - android:textSize="14sp" /> + android:id="@+id/summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:gravity="center" + android:textColor="?android:attr/textColorSecondary" + android:textSize="14sp" /> <RelativeLayout - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1"> + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/device_list" @@ -51,30 +51,39 @@ android:scrollbars="vertical" android:layout_height="200dp" /> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/permission_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </RelativeLayout> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:gravity="end"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginTop="24dp"> <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> <Button - android:id="@+id/btn_negative" - style="@android:style/Widget.Material.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/consent_no" - android:textColor="?android:attr/textColorSecondary" /> + android:id="@+id/btn_negative" + style="@style/NegativeButton" + android:text="@string/consent_no" /> + + <Button + android:id="@+id/btn_positive" + style="@style/PositiveButton" + android:text="@string/consent_yes" /> <Button - android:id="@+id/btn_positive" - style="@android:style/Widget.Material.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/consent_yes" /> + android:id="@+id/btn_negative_multiple_devices" + android:layout_marginLeft="170dp" + android:layout_marginBottom="10dp" + style="@style/NegativeButtonMultipleDevices" + android:textColor = "?android:textColorPrimary" + android:visibility="gone" + android:text="@string/consent_no" /> </LinearLayout> diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml index a1855fdda78d..7c508147e0ac 100644 --- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml @@ -28,45 +28,40 @@ <!-- 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" - android:layout_height="wrap_content" - android:gravity="center" - android:paddingHorizontal="12dp" - style="@*android:style/TextAppearance.Widget.Toolbar.Title"/> + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingHorizontal="12dp" + style="@*android:style/TextAppearance.Widget.Toolbar.Title"/> <TextView - android:id="@+id/summary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:gravity="center" - android:textColor="?android:attr/textColorSecondary" - android:textSize="14sp" /> + android:id="@+id/summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:gravity="center" + android:textColor="?android:attr/textColorSecondary" + android:textSize="14sp" /> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:gravity="end"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginTop="24dp"> <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> <Button - android:id="@+id/btn_negative" - style="@android:style/Widget.Material.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/consent_no" - android:textColor="?android:attr/textColorSecondary" /> + android:id="@+id/btn_negative" + style="@style/NegativeButton" + android:text="@string/consent_no" /> <Button - android:id="@+id/btn_positive" - style="@android:style/Widget.Material.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/consent_yes" /> + android:id="@+id/btn_positive" + style="@style/PositiveButton" + android:text="@string/consent_yes" /> </LinearLayout> diff --git a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml index 8fd1fb0de891..c177039891d2 100644 --- a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml @@ -60,4 +60,4 @@ </LinearLayout> -</LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml index 153fc1f35abe..b732b1bd9ad8 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml @@ -25,16 +25,16 @@ <!-- 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"/> + 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"/> + 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/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml new file mode 100644 index 000000000000..b8a0f7938f0b --- /dev/null +++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml @@ -0,0 +1,55 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_item_permission" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="5dp"> + + <ImageView + android:id="@+id/permission_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginTop="7dp" + android:layout_marginEnd="12dp" + android:contentDescription="Permission Icon"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_vertical" + android:padding="6dp"> + + <TextView + android:id="@+id/permission_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="16sp" + android:textAppearance="?android:attr/textAppearanceListItemSmall"/> + + <TextView + android:id="@+id/permission_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textColor="?android:attr/textColorSecondary"/> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values-night/themes.xml b/packages/CompanionDeviceManager/res/values-night/themes.xml new file mode 100644 index 000000000000..6eb16e726321 --- /dev/null +++ b/packages/CompanionDeviceManager/res/values-night/themes.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + + <style name="ChooserActivity" + parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> + <item name="*android:windowFixedHeightMajor">100%</item> + <item name="*android:windowFixedHeightMinor">100%</item> + <item name="android:windowBackground">@android:color/transparent</item> + </style> + +</resources> diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 9626751679e1..55a199813388 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -38,8 +38,14 @@ <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= --> + <!-- Apps permission will be granted of APP_STREAMING profile [CHAR LIMIT=30] --> + <string name="permission_apps">Apps</string> + + <!-- Description of apps permission of APP_STREAMING profile [CHAR LIMIT=NONE] --> + <string name="permission_apps_summary">Stream your phone\u2019s apps</string> + <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] --> - <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to access this information for your phone</string> + <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to access this information from your phone</string> <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] --> <string name="summary_app_streaming" product="default">Let <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to provide <strong><xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g></strong> remote access to access to applications installed on this phone when connected.</string> @@ -72,6 +78,18 @@ <!-- Description of the privileges the application will get if associated with the companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] --> <string name="summary_computer"></string> + <!-- Notification permission will be granted of COMPUTER profile [CHAR LIMIT=30] --> + <string name="permission_notification">Notifications</string> + + <!-- Description of notification permission of COMPUTER profile [CHAR LIMIT=NONE] --> + <string name="permission_notification_summary">Can read all notifications, including information like contracts, messages, and photos</string> + + <!-- Storage permission will be granted of COMPUTER profile [CHAR LIMIT=30] --> + <string name="permission_storage">Photos and media</string> + + <!-- Description of storage permission of COMPUTER profile [CHAR LIMIT=NONE] --> + <string name="permission_storage_summary"></string> + <!-- Title of the helper dialog for COMPUTER profile [CHAR LIMIT=30]. --> <string name="helper_title_computer">Google Play services</string> diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index bba45e9ecf0f..4a267db3ceec 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -35,4 +35,34 @@ <item name="android:background">@drawable/helper_ok_button</item> </style> + <style name="NegativeButton" + parent="@android:style/Widget.Material.Button.Borderless.Colored"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textAllCaps">false</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/system_neutral1_900</item> + <item name="android:background">@drawable/btn_negative_top</item> + </style> + + <style name="PositiveButton" + parent="@android:style/Widget.Material.Button.Borderless.Colored"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textAllCaps">false</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/system_neutral1_900</item> + <item name="android:layout_marginTop">4dp</item> + <item name="android:background">@drawable/btn_positive_bottom</item> + </style> + + <style name="NegativeButtonMultipleDevices" + parent="@android:style/Widget.Material.Button.Colored"> + <item name="android:layout_width">100dp</item> + <item name="android:layout_height">35dp</item> + <item name="android:layout_marginTop">20dp</item> + <item name="android:textAllCaps">false</item> + <item name="android:background">@drawable/btn_negative_multiple_devices</item> + </style> + </resources>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/themes.xml b/packages/CompanionDeviceManager/res/values/themes.xml index 72404322c25e..e3fc67c50d75 100644 --- a/packages/CompanionDeviceManager/res/values/themes.xml +++ b/packages/CompanionDeviceManager/res/values/themes.xml @@ -17,11 +17,10 @@ <resources> <style name="ChooserActivity" - parent="@android:style/Theme.DeviceDefault.Light.Dialog"> + parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar"> <item name="*android:windowFixedHeightMajor">100%</item> <item name="*android:windowFixedHeightMinor">100%</item> <item name="android:windowBackground">@android:color/transparent</item> - <item name="android:forceDarkAllowed">true</item> </style> </resources> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 0fba250b200a..0ab126a7dae0 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -24,6 +24,9 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT; +import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_APPS; +import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_NOTIFICATION; +import static com.android.companiondevicemanager.PermissionListAdapter.TYPE_STORAGE; import static com.android.companiondevicemanager.Utils.getApplicationLabel; import static com.android.companiondevicemanager.Utils.getHtmlFromResources; import static com.android.companiondevicemanager.Utils.getVendorHeaderIcon; @@ -61,6 +64,7 @@ import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; import java.util.List; /** @@ -114,16 +118,21 @@ public class CompanionDeviceActivity extends FragmentActivity implements // Present for self-managed association requests and "single-device" regular association // regular. private Button mButtonAllow; - // Present for all associations. private Button mButtonNotAllow; + // Present for multiple devices association requests only. + private Button mButtonNotAllowMultipleDevices; private LinearLayout mAssociationConfirmationDialog; private RelativeLayout mVendorHeader; // The recycler view is only shown for multiple-device regular association request, after // at least one matching device is found. - private @Nullable RecyclerView mRecyclerView; - private @Nullable DeviceListAdapter mAdapter; + private @Nullable RecyclerView mDeviceListRecyclerView; + private @Nullable DeviceListAdapter mDeviceAdapter; + + // The recycler view is only shown for selfManaged association request. + private @Nullable RecyclerView mPermissionListRecyclerView; + private @Nullable PermissionListAdapter mPermissionListAdapter; // The flag used to prevent double taps, that may lead to sending several requests for creating // an association to CDM. @@ -133,6 +142,8 @@ public class CompanionDeviceActivity extends FragmentActivity implements // onActivityResult() after the association is created. private @Nullable DeviceFilterPair<?> mSelectedDevice; + private @Nullable List<Integer> mPermissionTypes; + @Override public void onCreate(Bundle savedInstanceState) { if (DEBUG) Log.d(TAG, "onCreate()"); @@ -242,14 +253,20 @@ public class CompanionDeviceActivity extends FragmentActivity implements mVendorHeaderName = findViewById(R.id.vendor_header_name); mVendorHeaderButton = findViewById(R.id.vendor_header_button); - mRecyclerView = findViewById(R.id.device_list); - mAdapter = new DeviceListAdapter(this, this::onListItemClick); + mDeviceListRecyclerView = findViewById(R.id.device_list); + mDeviceAdapter = new DeviceListAdapter(this, this::onListItemClick); + + mPermissionListRecyclerView = findViewById(R.id.permission_list); + mPermissionListAdapter = new PermissionListAdapter(this); mButtonAllow = findViewById(R.id.btn_positive); mButtonNotAllow = findViewById(R.id.btn_negative); + mButtonNotAllowMultipleDevices = findViewById(R.id.btn_negative_multiple_devices); mButtonAllow.setOnClickListener(this::onPositiveButtonClick); mButtonNotAllow.setOnClickListener(this::onNegativeButtonClick); + mButtonNotAllowMultipleDevices.setOnClickListener(this::onNegativeButtonClick); + mVendorHeaderButton.setOnClickListener(this::onShowHelperDialog); if (mRequest.isSelfManaged()) { @@ -359,7 +376,8 @@ public class CompanionDeviceActivity extends FragmentActivity implements final Drawable vendorIcon; final CharSequence vendorName; final Spanned title; - final Spanned summary; + + mPermissionTypes = new ArrayList<>(); try { vendorIcon = getVendorHeaderIcon(this, packageName, userId); @@ -372,33 +390,36 @@ public class CompanionDeviceActivity extends FragmentActivity implements switch (deviceProfile) { case DEVICE_PROFILE_APP_STREAMING: - title = getHtmlFromResources(this, R.string.title_app_streaming, appLabel); - summary = getHtmlFromResources( - this, R.string.summary_app_streaming, appLabel, deviceName); + title = getHtmlFromResources(this, R.string.title_app_streaming, deviceName); + mPermissionTypes.add(TYPE_APPS); break; case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION: - title = getHtmlFromResources(this, R.string.title_automotive_projection, appLabel); - summary = getHtmlFromResources( - this, R.string.summary_automotive_projection, appLabel, deviceName); + title = getHtmlFromResources( + this, R.string.title_automotive_projection, deviceName); break; case DEVICE_PROFILE_COMPUTER: - title = getHtmlFromResources(this, R.string.title_computer, appLabel); - summary = getHtmlFromResources( - this, R.string.summary_computer, appLabel, deviceName); + title = getHtmlFromResources(this, R.string.title_computer, deviceName); + mPermissionTypes.add(TYPE_NOTIFICATION); + mPermissionTypes.add(TYPE_STORAGE); break; default: throw new RuntimeException("Unsupported profile " + deviceProfile); } + mSummary.setVisibility(View.GONE); + + mPermissionListAdapter.setPermissionType(mPermissionTypes); + mPermissionListRecyclerView.setAdapter(mPermissionListAdapter); + mPermissionListRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mTitle.setText(title); - mSummary.setText(summary); mVendorHeaderImage.setImageDrawable(vendorIcon); mVendorHeaderName.setText(vendorName); - mRecyclerView.setVisibility(View.GONE); + mDeviceListRecyclerView.setVisibility(View.GONE); mVendorHeader.setVisibility(View.VISIBLE); } @@ -411,7 +432,8 @@ public class CompanionDeviceActivity extends FragmentActivity implements deviceFilterPairs -> updateSingleDeviceUi( deviceFilterPairs, deviceProfile, appLabel)); - mRecyclerView.setVisibility(View.GONE); + mPermissionListRecyclerView.setVisibility(View.GONE); + mDeviceListRecyclerView.setVisibility(View.GONE); } private void updateSingleDeviceUi(List<DeviceFilterPair<?>> deviceFilterPairs, @@ -460,31 +482,33 @@ public class CompanionDeviceActivity extends FragmentActivity implements mTitle.setText(title); mSummary.setText(summary); - mAdapter = new DeviceListAdapter(this, this::onListItemClick); + mDeviceAdapter = new DeviceListAdapter(this, this::onListItemClick); // TODO: hide the list and show a spinner until a first device matching device is found. - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mDeviceListRecyclerView.setAdapter(mDeviceAdapter); + mDeviceListRecyclerView.setLayoutManager(new LinearLayoutManager(this)); CompanionDeviceDiscoveryService.getScanResult().observe( /* lifecycleOwner */ this, - /* observer */ mAdapter); + /* observer */ mDeviceAdapter); // "Remove" consent button: users would need to click on the list item. mButtonAllow.setVisibility(View.GONE); + mButtonNotAllow.setVisibility(View.GONE); + mButtonNotAllowMultipleDevices.setVisibility(View.VISIBLE); } private void onListItemClick(int position) { if (DEBUG) Log.d(TAG, "onListItemClick() " + position); - final DeviceFilterPair<?> selectedDevice = mAdapter.getItem(position); + final DeviceFilterPair<?> selectedDevice = mDeviceAdapter.getItem(position); if (mSelectedDevice != null) { if (DEBUG) Log.w(TAG, "Already selected."); return; } // Notify the adapter to highlight the selected item. - mAdapter.setSelectedPosition(position); + mDeviceAdapter.setSelectedPosition(position); mSelectedDevice = requireNonNull(selectedDevice); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java index e5513b074865..8babd3ade1eb 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java @@ -15,9 +15,10 @@ */ package com.android.companiondevicemanager; + +import static com.android.companiondevicemanager.Utils.getIcon; + import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -60,11 +61,11 @@ class DeviceListAdapter extends RecyclerView.Adapter<DeviceListAdapter.ViewHolde R.layout.list_item_device, parent, false); ViewHolder viewHolder = new ViewHolder(view); if (viewType == TYPE_WIFI) { - viewHolder.mImageView.setImageDrawable(getIcon( - com.android.internal.R.drawable.ic_wifi_signal_3)); + viewHolder.mImageView.setImageDrawable( + getIcon(mContext, com.android.internal.R.drawable.ic_wifi_signal_3)); } else { - viewHolder.mImageView.setImageDrawable(getIcon( - android.R.drawable.stat_sys_data_bluetooth)); + viewHolder.mImageView.setImageDrawable( + getIcon(mContext, android.R.drawable.stat_sys_data_bluetooth)); } return viewHolder; } @@ -115,12 +116,6 @@ class DeviceListAdapter extends RecyclerView.Adapter<DeviceListAdapter.ViewHolde return mDevices.get(position).getDevice() instanceof android.net.wifi.ScanResult; } - private Drawable getIcon(int resId) { - Drawable icon = mContext.getResources().getDrawable(resId, null); - icon.setTint(Color.DKGRAY); - return icon; - } - public interface OnItemClickListener { void onItemClick(int position); } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java new file mode 100644 index 000000000000..895b729ea8c7 --- /dev/null +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.companiondevicemanager; + +import static com.android.companiondevicemanager.Utils.getHtmlFromResources; +import static com.android.companiondevicemanager.Utils.getIcon; + +import static java.util.Collections.unmodifiableMap; + +import android.content.Context; +import android.text.Spanned; +import android.util.ArrayMap; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; +import java.util.Map; + +class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.ViewHolder> { + private final Context mContext; + + private List<Integer> mPermissions; + + static final int TYPE_NOTIFICATION = 0; + static final int TYPE_STORAGE = 1; + static final int TYPE_APPS = 2; + + private static final Map<Integer, Integer> sTitleMap; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(TYPE_NOTIFICATION, R.string.permission_notification); + map.put(TYPE_STORAGE, R.string.permission_storage); + map.put(TYPE_APPS, R.string.permission_apps); + sTitleMap = unmodifiableMap(map); + } + + private static final Map<Integer, Integer> sSummaryMap; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(TYPE_NOTIFICATION, R.string.permission_notification_summary); + map.put(TYPE_STORAGE, R.string.permission_storage_summary); + map.put(TYPE_APPS, R.string.permission_apps_summary); + sSummaryMap = unmodifiableMap(map); + } + + private static final Map<Integer, Integer> sIconMap; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(TYPE_NOTIFICATION, R.drawable.ic_notifications); + map.put(TYPE_STORAGE, R.drawable.ic_storage); + map.put(TYPE_APPS, R.drawable.ic_apps); + sIconMap = unmodifiableMap(map); + } + + PermissionListAdapter(Context context) { + mContext = context; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate( + R.layout.list_item_permission, parent, false); + ViewHolder viewHolder = new ViewHolder(view); + viewHolder.mPermissionIcon.setImageDrawable(getIcon(mContext, sIconMap.get(viewType))); + + return viewHolder; + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + int type = getItemViewType(position); + final Spanned title = getHtmlFromResources(mContext, sTitleMap.get(type)); + final Spanned summary = getHtmlFromResources(mContext, sSummaryMap.get(type)); + + holder.mPermissionName.setText(title); + holder.mPermissionSummary.setText(summary); + } + + @Override + public int getItemViewType(int position) { + return mPermissions.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return mPermissions != null ? mPermissions.size() : 0; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + private final TextView mPermissionName; + private final TextView mPermissionSummary; + private final ImageView mPermissionIcon; + ViewHolder(View itemView) { + super(itemView); + mPermissionName = itemView.findViewById(R.id.permission_name); + mPermissionSummary = itemView.findViewById(R.id.permission_summary); + mPermissionIcon = itemView.findViewById(R.id.permission_icon); + } + } + + void setPermissionType(List<Integer> permissions) { + mPermissions = permissions; + } +} diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java index 76bbcfb79155..d5b2f0a748f1 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ApplicationInfoFlags; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -121,6 +122,12 @@ class Utils { return appInfo; } + static @NonNull Drawable getIcon(@NonNull Context context, int resId) { + Drawable icon = context.getResources().getDrawable(resId, null); + icon.setTint(Color.DKGRAY); + return icon; + } + static void runOnMainThread(Runnable runnable) { if (Thread.currentThread() == Looper.getMainLooper().getThread()) { runnable.run(); diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java index a6269711055a..43f4c40f2d27 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkUpdateRequest.java @@ -24,36 +24,52 @@ import android.os.Parcelable; import java.util.Objects; -/** @hide */ +/** + * Represents a request to update an existing Ethernet interface. + * + * @see EthernetManager#updateConfiguration + * + * @hide + */ @SystemApi public final class EthernetNetworkUpdateRequest implements Parcelable { @NonNull private final IpConfiguration mIpConfig; - @NonNull + @Nullable private final NetworkCapabilities mNetworkCapabilities; + /** + * @return the new {@link IpConfiguration}. + */ @NonNull public IpConfiguration getIpConfiguration() { return new IpConfiguration(mIpConfig); } - @NonNull + /** + * Setting the {@link NetworkCapabilities} is optional in {@link EthernetNetworkUpdateRequest}. + * When set to null, the existing NetworkCapabilities are not updated. + * + * @return the new {@link NetworkCapabilities} or null. + */ + @Nullable public NetworkCapabilities getNetworkCapabilities() { - return new NetworkCapabilities(mNetworkCapabilities); + return mNetworkCapabilities == null ? null : new NetworkCapabilities(mNetworkCapabilities); } private EthernetNetworkUpdateRequest(@NonNull final IpConfiguration ipConfig, - @NonNull final NetworkCapabilities networkCapabilities) { + @Nullable final NetworkCapabilities networkCapabilities) { Objects.requireNonNull(ipConfig); - Objects.requireNonNull(networkCapabilities); - mIpConfig = new IpConfiguration(ipConfig); - mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); + mIpConfig = ipConfig; + mNetworkCapabilities = networkCapabilities; } private EthernetNetworkUpdateRequest(@NonNull final Parcel source) { Objects.requireNonNull(source); - mIpConfig = IpConfiguration.CREATOR.createFromParcel(source); - mNetworkCapabilities = NetworkCapabilities.CREATOR.createFromParcel(source); + mIpConfig = source.readParcelable(IpConfiguration.class.getClassLoader(), + IpConfiguration.class); + mNetworkCapabilities = source.readParcelable(NetworkCapabilities.class.getClassLoader(), + NetworkCapabilities.class); } /** @@ -75,7 +91,8 @@ public final class EthernetNetworkUpdateRequest implements Parcelable { public Builder(@NonNull final EthernetNetworkUpdateRequest request) { Objects.requireNonNull(request); mBuilderIpConfig = new IpConfiguration(request.mIpConfig); - mBuilderNetworkCapabilities = new NetworkCapabilities(request.mNetworkCapabilities); + mBuilderNetworkCapabilities = null == request.mNetworkCapabilities + ? null : new NetworkCapabilities(request.mNetworkCapabilities); } /** @@ -85,7 +102,6 @@ public final class EthernetNetworkUpdateRequest implements Parcelable { */ @NonNull public Builder setIpConfiguration(@NonNull final IpConfiguration ipConfig) { - Objects.requireNonNull(ipConfig); mBuilderIpConfig = new IpConfiguration(ipConfig); return this; } @@ -96,9 +112,8 @@ public final class EthernetNetworkUpdateRequest implements Parcelable { * @return The builder to facilitate chaining. */ @NonNull - public Builder setNetworkCapabilities(@NonNull final NetworkCapabilities nc) { - Objects.requireNonNull(nc); - mBuilderNetworkCapabilities = new NetworkCapabilities(nc); + public Builder setNetworkCapabilities(@Nullable final NetworkCapabilities nc) { + mBuilderNetworkCapabilities = nc == null ? null : new NetworkCapabilities(nc); return this; } @@ -135,8 +150,8 @@ public final class EthernetNetworkUpdateRequest implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - mIpConfig.writeToParcel(dest, flags); - mNetworkCapabilities.writeToParcel(dest, flags); + dest.writeParcelable(mIpConfig, flags); + dest.writeParcelable(mNetworkCapabilities, flags); } @Override diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java index 151c90dd4155..3b93f1a1905f 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java @@ -25,9 +25,9 @@ import static android.net.NetworkStats.UID_ALL; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.net.ConnectivityManager; import android.net.NetworkStats; import android.net.UnderlyingNetworkInfo; +import android.os.ServiceSpecificException; import android.os.StrictMode; import android.os.SystemClock; @@ -35,6 +35,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ProcFileReader; import com.android.net.module.util.CollectionUtils; +import com.android.server.BpfNetMaps; import libcore.io.IoUtils; @@ -74,6 +75,8 @@ public class NetworkStatsFactory { private final Context mContext; + private final BpfNetMaps mBpfNetMaps; + /** * Guards persistent data access in this class * @@ -170,6 +173,7 @@ public class NetworkStatsFactory { mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt"); mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats"); mUseBpfStats = useBpfStats; + mBpfNetMaps = new BpfNetMaps(); synchronized (mPersistentDataLock) { mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1); mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1); @@ -297,12 +301,14 @@ public class NetworkStatsFactory { } @GuardedBy("mPersistentDataLock") - private void requestSwapActiveStatsMapLocked() { - // Do a active map stats swap. When the binder call successfully returns, - // the system server should be able to safely read and clean the inactive map - // without race problem. - final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); - cm.swapActiveStatsMap(); + private void requestSwapActiveStatsMapLocked() throws IOException { + try { + // Do a active map stats swap. Once the swap completes, this code + // can read and clean the inactive map without races. + mBpfNetMaps.swapActiveStatsMap(); + } catch (ServiceSpecificException e) { + throw new IOException(e); + } } /** @@ -328,11 +334,7 @@ public class NetworkStatsFactory { final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0 /* initialSize */); if (mUseBpfStats) { - try { - requestSwapActiveStatsMapLocked(); - } catch (RuntimeException e) { - throw new IOException(e); - } + requestSwapActiveStatsMapLocked(); // Stats are always read from the inactive map, so they must be read after the // swap if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL, diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index b5e05395f2e8..1d22908e1393 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -565,7 +565,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return new BpfMap<U32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U8.class); } catch (ErrnoException e) { - Log.wtf(TAG, "Cannot create uid counter set map: " + e); + Log.wtf(TAG, "Cannot open uid counter set map: " + e); return null; } } @@ -576,7 +576,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return new BpfMap<CookieTagMapKey, CookieTagMapValue>(COOKIE_TAG_MAP_PATH, BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class); } catch (ErrnoException e) { - Log.wtf(TAG, "Cannot create cookie tag map: " + e); + Log.wtf(TAG, "Cannot open cookie tag map: " + e); return null; } } @@ -587,7 +587,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_A_PATH, BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class); } catch (ErrnoException e) { - Log.wtf(TAG, "Cannot create stats map A: " + e); + Log.wtf(TAG, "Cannot open stats map A: " + e); return null; } } @@ -598,7 +598,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_B_PATH, BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class); } catch (ErrnoException e) { - Log.wtf(TAG, "Cannot create stats map B: " + e); + Log.wtf(TAG, "Cannot open stats map B: " + e); return null; } } @@ -609,7 +609,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return new BpfMap<UidStatsMapKey, StatsMapValue>(APP_UID_STATS_MAP_PATH, BpfMap.BPF_F_RDWR, UidStatsMapKey.class, StatsMapValue.class); } catch (ErrnoException e) { - Log.wtf(TAG, "Cannot create app uid stats map: " + e); + Log.wtf(TAG, "Cannot open app uid stats map: " + e); return null; } } diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java index 44b3b4e8488a..706aba3d156f 100644 --- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java +++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java @@ -51,27 +51,34 @@ public class ActivityEmbeddingUtils { } /** - * Whether current activity is embedded in the Settings app or not. + * Whether the current activity is embedded in the Settings app or not. + * + * @param activity Activity that needs the check */ public static boolean isActivityEmbedded(Activity activity) { return SplitController.getInstance().isActivityEmbedded(activity); } /** - * Whether current activity is suggested to show back button or not. + * Whether the current activity should hide the navigate up button. + * + * @param activity Activity that needs the check + * @param isSecondLayerPage indicates if the activity(page) is shown in the 2nd layer of + * Settings app */ - public static boolean shouldHideBackButton(Activity activity, boolean isSecondaryLayerPage) { + public static boolean shouldHideNavigateUpButton(Activity activity, boolean isSecondLayerPage) { if (!BuildCompat.isAtLeastT()) { return false; } - if (!isSecondaryLayerPage) { + if (!isSecondLayerPage) { return false; } - final String shouldHideBackButton = Settings.Global.getString(activity.getContentResolver(), - "settings_hide_secondary_page_back_button_in_two_pane"); + final String shouldHideNavigateUpButton = + Settings.Global.getString(activity.getContentResolver(), + "settings_hide_second_layer_page_navigate_up_button_in_two_pane"); - if (TextUtils.isEmpty(shouldHideBackButton) - || TextUtils.equals("true", shouldHideBackButton)) { + if (TextUtils.isEmpty(shouldHideNavigateUpButton) + || Boolean.parseBoolean(shouldHideNavigateUpButton)) { return isActivityEmbedded(activity); } return false; diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SettingsLib/res/drawable/ic_account_circle.xml index 5ca99f32771b..5ca99f32771b 100644 --- a/packages/SystemUI/res/drawable/ic_account_circle.xml +++ b/packages/SettingsLib/res/drawable/ic_account_circle.xml diff --git a/packages/SystemUI/res/drawable/ic_account_circle_filled.xml b/packages/SettingsLib/res/drawable/ic_account_circle_filled.xml index 47c553b52123..47c553b52123 100644 --- a/packages/SystemUI/res/drawable/ic_account_circle_filled.xml +++ b/packages/SettingsLib/res/drawable/ic_account_circle_filled.xml diff --git a/packages/SystemUI/res/drawable/ic_add_supervised_user.xml b/packages/SettingsLib/res/drawable/ic_add_supervised_user.xml index 627743ed1669..627743ed1669 100644 --- a/packages/SystemUI/res/drawable/ic_add_supervised_user.xml +++ b/packages/SettingsLib/res/drawable/ic_add_supervised_user.xml diff --git a/packages/SystemUI/res/drawable/kg_bg_avatar.xml b/packages/SettingsLib/res/drawable/user_avatar_bg.xml index addb3f7508f5..1f50496f3a5c 100644 --- a/packages/SystemUI/res/drawable/kg_bg_avatar.xml +++ b/packages/SettingsLib/res/drawable/user_avatar_bg.xml @@ -22,7 +22,7 @@ android:viewportHeight="100"> <path - android:fillColor="@color/kg_user_switcher_avatar_background" + android:fillColor="@color/user_avatar_color_bg" android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/> </vector> diff --git a/packages/SettingsLib/res/values-v31/styles.xml b/packages/SettingsLib/res/values-v31/styles.xml index 343de2cdf47a..0b703c99884b 100644 --- a/packages/SettingsLib/res/values-v31/styles.xml +++ b/packages/SettingsLib/res/values-v31/styles.xml @@ -15,7 +15,8 @@ limitations under the License. --> <resources> - <style name="SettingsLibTabsTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> + <style name="SettingsLibTabsTextAppearance" + parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"> <item name="android:textSize">16sp</item> </style> @@ -25,6 +26,7 @@ <item name="android:layout_height">48dp</item> <item name="android:layout_marginStart">?android:attr/listPreferredItemPaddingStart</item> <item name="android:layout_marginEnd">?android:attr/listPreferredItemPaddingEnd</item> + <item name="tabMaxWidth">0dp</item> <item name="tabGravity">fill</item> <item name="tabBackground">@drawable/settingslib_tabs_background</item> <item name="tabIndicator">@drawable/settingslib_tabs_indicator_background</item> diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml index 5e8779fa289a..7ab2ed9481b7 100644 --- a/packages/SettingsLib/res/values/colors.xml +++ b/packages/SettingsLib/res/values/colors.xml @@ -36,7 +36,8 @@ <color name="bt_color_bg_6">#e9d2fd</color> <!-- Material Purple 100 --> <color name="bt_color_bg_7">#cbf0f8</color> <!-- Material Cyan 100 --> - <color name="dark_mode_icon_color_single_tone">#99000000</color> <color name="light_mode_icon_color_single_tone">#ffffff</color> + + <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color> </resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index da0381b68278..042fef23cbe6 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1426,8 +1426,12 @@ <string name="user_switch_to_user">Switch to <xliff:g id="user_name" example="John Doe">%s</xliff:g></string> <!-- Dialog message when creating a new user [CHAR LIMIT=NONE] --> <string name="creating_new_user_dialog_message">Creating new user…</string> + <!-- Dialog message when creating a new guest [CHAR LIMIT=NONE] --> + <string name="creating_new_guest_dialog_message">Creating new guest…</string> <!-- Text shown to notify that the creation of new user has failed. [CHAR LIMIT=40] --> <string name="add_user_failed">Failed to create a new user</string> + <!-- Text shown to notify that the creation of new guest has failed. [CHAR LIMIT=40] --> + <string name="add_guest_failed">Failed to create a new guest</string> <!-- Title for the preference to enter the nickname of the user to display in the user switcher [CHAR LIMIT=25]--> <string name="user_nickname">Nickname</string> @@ -1568,4 +1572,10 @@ <!-- Content description for a default user icon. [CHAR LIMIT=NONE] --> <string name="default_user_icon_description">Default user icon</string> + <!-- Title for the 'physical keyboard' settings screen. [CHAR LIMIT=35] --> + <string name="physical_keyboard_title">Physical keyboard</string> + <!-- Title for the keyboard layout preference dialog. [CHAR LIMIT=35] --> + <string name="keyboard_layout_dialog_title">Choose keyboard layout</string> + <!-- Label of the default keyboard layout. [CHAR LIMIT=35] --> + <string name="keyboard_layout_default_label">Default</string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index fcb56d2f7016..01d0cc40c0bc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -248,9 +248,6 @@ public class DreamBackend { } public @WhenToDream int getWhenToDreamSetting() { - if (!isEnabled()) { - return NEVER; - } return isActivatedOnDock() && isActivatedOnSleep() ? EITHER : isActivatedOnDock() ? WHILE_DOCKED : isActivatedOnSleep() ? WHILE_CHARGING diff --git a/packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java b/packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java index 075635c87b1b..dd86bec9126c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java @@ -31,11 +31,15 @@ import com.android.settingslib.R; public class UserCreatingDialog extends AlertDialog { public UserCreatingDialog(Context context) { + this(context, false); + } + + public UserCreatingDialog(Context context, boolean isGuest) { // hardcoding theme to be consistent with UserSwitchingDialog's theme // todo replace both to adapt to the device's theme super(context, com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog_Alert); - inflateContent(); + inflateContent(isGuest); getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); WindowManager.LayoutParams attrs = getWindow().getAttributes(); @@ -44,12 +48,14 @@ public class UserCreatingDialog extends AlertDialog { getWindow().setAttributes(attrs); } - private void inflateContent() { + private void inflateContent(boolean isGuest) { // using the same design as UserSwitchingDialog setCancelable(false); View view = LayoutInflater.from(getContext()) .inflate(R.layout.user_creation_progress_dialog, null); - String message = getContext().getString(R.string.creating_new_user_dialog_message); + String message = getContext().getString(isGuest + ? R.string.creating_new_guest_dialog_message + : R.string.creating_new_user_dialog_message); view.setAccessibilityPaneTitle(message); ((TextView) view.findViewById(R.id.message)).setText(message); setView(view); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index 4ab6542d567a..9ef6bdf0fb41 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -43,6 +43,31 @@ public class WifiUtils { private static final int INVALID_RSSI = -127; /** + * The intent action shows Wi-Fi dialog to connect Wi-Fi network. + * <p> + * Input: The calling package should put the chosen + * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into + * the {@link #EXTRA_CHOSEN_WIFI_ENTRY_KEY}. + * <p> + * Output: Nothing. + */ + @VisibleForTesting + static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG"; + + /** + * Specify a key that indicates the WifiEntry to be configured. + */ + @VisibleForTesting + static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; + + /** + * The lookup key for a boolean that indicates whether a chosen WifiEntry request to connect to. + * {@code true} means a chosen WifiEntry request to connect to. + */ + @VisibleForTesting + static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"; + + /** * The intent action shows network details settings to allow configuration of Wi-Fi. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -325,6 +350,19 @@ public class WifiUtils { } /** + * Returns the Intent for Wi-Fi dialog. + * + * @param key The Wi-Fi entry key + * @param connectForCaller True if a chosen WifiEntry request to connect to + */ + public static Intent getWifiDialogIntent(String key, boolean connectForCaller) { + final Intent intent = new Intent(ACTION_WIFI_DIALOG); + intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, key); + intent.putExtra(EXTRA_CONNECT_FOR_CALLER, connectForCaller); + return intent; + } + + /** * Returns the Intent for Wi-Fi network details settings. * * @param key The Wi-Fi entry key diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java index 9ebdba300266..1d08711715c3 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AvatarPhotoControllerTest.java @@ -150,23 +150,6 @@ public class AvatarPhotoControllerTest { } @Test - public void takePhotoIsNotFollowedByCropIntentWhenCropNotSupported() throws IOException { - when(mMockAvatarUi.startSystemActivityForResult(any(), anyInt())).thenReturn(false); - - File file = new File(mImagesDir, "file.txt"); - saveBitmapToFile(file); - - Intent intent = new Intent(); - intent.setData(Uri.parse( - "content://com.android.settingslib.test/my_cache/multi_user/file.txt")); - mController.onActivityResult( - REQUEST_CODE_TAKE_PHOTO, Activity.RESULT_OK, intent); - - verify(mMockAvatarUi, never()).startActivityForResult(any(), anyInt()); - verify(mMockAvatarUi, never()).startSystemActivityForResult(any(), anyInt()); - } - - @Test public void choosePhotoIsFollowedByCrop() throws IOException { new File(mImagesDir, "file.txt").createNewFile(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java index e7b3fe9ab8da..69561055ff7f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java @@ -156,6 +156,27 @@ public class WifiUtilsTest { } @Test + public void getWifiDialogIntent_returnsCorrectValues() { + String key = "test_key"; + + // Test that connectForCaller is true. + Intent intent = WifiUtils.getWifiDialogIntent(key, true /* connectForCaller */); + + assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DIALOG); + assertThat(intent.getStringExtra(WifiUtils.EXTRA_CHOSEN_WIFI_ENTRY_KEY)).isEqualTo(key); + assertThat(intent.getBooleanExtra(WifiUtils.EXTRA_CONNECT_FOR_CALLER, true)) + .isEqualTo(true /* connectForCaller */); + + // Test that connectForCaller is false. + intent = WifiUtils.getWifiDialogIntent(key, false /* connectForCaller */); + + assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DIALOG); + assertThat(intent.getStringExtra(WifiUtils.EXTRA_CHOSEN_WIFI_ENTRY_KEY)).isEqualTo(key); + assertThat(intent.getBooleanExtra(WifiUtils.EXTRA_CONNECT_FOR_CALLER, true)) + .isEqualTo(false /* connectForCaller */); + } + + @Test public void getWifiDetailsSettingsIntent_returnsCorrectValues() { final String key = "test_key"; diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index fa3360cd31f6..cbd71c02baf4 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -328,7 +328,5 @@ public class SecureSettingsValidators { return true; }); VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR); - VALIDATORS.put(Secure.FAST_PAIR_SCAN_ENABLED, BOOLEAN_VALIDATOR); - } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 1c5bf81faf3e..3c29a803ffbb 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2252,12 +2252,14 @@ class SettingsProtoDumpUtil { Settings.Secure.MULTI_PRESS_TIMEOUT, SecureSettingsProto.MULTI_PRESS_TIMEOUT); + final long navBar = p.start(SecureSettingsProto.NAV_BAR); dumpSetting(s, p, Settings.Secure.NAV_BAR_FORCE_VISIBLE, SecureSettingsProto.NavBar.NAV_BAR_FORCE_VISIBLE); dumpSetting(s, p, Settings.Secure.NAV_BAR_KIDS_MODE, SecureSettingsProto.NavBar.NAV_BAR_KIDS_MODE); + p.end(navBar); dumpSetting(s, p, Settings.Secure.NAVIGATION_MODE, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 7f8b2f51754c..e24b2d6c6aa9 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -794,7 +794,7 @@ android:noHistory="true" android:showForAllUsers="true" android:finishOnTaskLaunch="true" - android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> @@ -805,7 +805,7 @@ android:showForAllUsers="true" android:finishOnTaskLaunch="true" android:launchMode="singleInstance" - android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml index ef60a248f79a..4e9a4be0060e 100644 --- a/packages/SystemUI/animation/res/values/ids.xml +++ b/packages/SystemUI/animation/res/values/ids.xml @@ -15,5 +15,14 @@ limitations under the License. --> <resources> + <!-- DialogLaunchAnimator --> <item type="id" name="launch_animation_running"/> + + <!-- ViewBoundsAnimator --> + <item type="id" name="tag_animator"/> + <item type="id" name="tag_layout_listener"/> + <item type="id" name="tag_override_bottom"/> + <item type="id" name="tag_override_left"/> + <item type="id" name="tag_override_right"/> + <item type="id" name="tag_override_top"/> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt new file mode 100644 index 000000000000..5593fdfe5f51 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ObjectAnimator +import android.animation.PropertyValuesHolder +import android.util.IntProperty +import android.view.View +import android.view.ViewGroup +import android.view.animation.Interpolator + +/** + * A class that allows changes in bounds within a view hierarchy to animate seamlessly between the + * start and end state. + */ +class ViewBoundAnimator { + // TODO(b/221418522): make this private once it can't be passed as an arg anymore. + enum class Bound(val label: String, val overrideTag: Int) { + LEFT("left", R.id.tag_override_left) { + override fun setValue(view: View, value: Int) { + view.left = value + } + + override fun getValue(view: View): Int { + return view.left + } + }, + TOP("top", R.id.tag_override_top) { + override fun setValue(view: View, value: Int) { + view.top = value + } + + override fun getValue(view: View): Int { + return view.top + } + }, + RIGHT("right", R.id.tag_override_right) { + override fun setValue(view: View, value: Int) { + view.right = value + } + + override fun getValue(view: View): Int { + return view.right + } + }, + BOTTOM("bottom", R.id.tag_override_bottom) { + override fun setValue(view: View, value: Int) { + view.bottom = value + } + + override fun getValue(view: View): Int { + return view.bottom + } + }; + + abstract fun setValue(view: View, value: Int) + abstract fun getValue(view: View): Int + } + + companion object { + /** Default values for the animation. These can all be overridden at call time. */ + private const val DEFAULT_DURATION = 500L + private val DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED + private val DEFAULT_BOUNDS = setOf(Bound.LEFT, Bound.TOP, Bound.RIGHT, Bound.BOTTOM) + + /** The properties used to animate the view bounds. */ + private val PROPERTIES = mapOf( + Bound.LEFT to createViewProperty(Bound.LEFT), + Bound.TOP to createViewProperty(Bound.TOP), + Bound.RIGHT to createViewProperty(Bound.RIGHT), + Bound.BOTTOM to createViewProperty(Bound.BOTTOM) + ) + + private fun createViewProperty(bound: Bound): IntProperty<View> { + return object : IntProperty<View>(bound.label) { + override fun setValue(view: View, value: Int) { + setBound(view, bound, value) + } + + override fun get(view: View): Int { + return getBound(view, bound) ?: bound.getValue(view) + } + } + } + + /** + * Instruct the animator to watch for changes to the layout of [rootView] and its children + * and animate them. The animation can be limited to a subset of [bounds]. It uses the + * given [interpolator] and [duration]. + * + * If a new layout change happens while an animation is already in progress, the animation + * is updated to continue from the current values to the new end state. + * + * The animator continues to respond to layout changes until [stopAnimating] is called. + * + * Successive calls to this method override the previous settings ([interpolator] and + * [duration]). The changes take effect on the next animation. + * + * TODO(b/221418522): remove the ability to select which bounds to animate and always + * animate all of them. + */ + @JvmOverloads + fun animate( + rootView: View, + bounds: Set<Bound> = DEFAULT_BOUNDS, + interpolator: Interpolator = DEFAULT_INTERPOLATOR, + duration: Long = DEFAULT_DURATION + ) { + animate(rootView, bounds, interpolator, duration, false /* ephemeral */) + } + + /** + * Like [animate], but only takes effect on the next layout update, then unregisters itself + * once the first animation is complete. + * + * TODO(b/221418522): remove the ability to select which bounds to animate and always + * animate all of them. + */ + @JvmOverloads + fun animateNextUpdate( + rootView: View, + bounds: Set<Bound> = DEFAULT_BOUNDS, + interpolator: Interpolator = DEFAULT_INTERPOLATOR, + duration: Long = DEFAULT_DURATION + ) { + animate(rootView, bounds, interpolator, duration, true /* ephemeral */) + } + + private fun animate( + rootView: View, + bounds: Set<Bound>, + interpolator: Interpolator, + duration: Long, + ephemeral: Boolean + ) { + val listener = createListener(bounds, interpolator, duration, ephemeral) + recursivelyAddListener(rootView, listener) + } + + /** + * Instruct the animator to stop watching for changes to the layout of [rootView] and its + * children. + * + * Any animations already in progress continue until their natural conclusion. + */ + fun stopAnimating(rootView: View) { + val listener = rootView.getTag(R.id.tag_layout_listener) + if (listener != null && listener is View.OnLayoutChangeListener) { + rootView.setTag(R.id.tag_layout_listener, null /* tag */) + rootView.removeOnLayoutChangeListener(listener) + } + + if (rootView is ViewGroup) { + for (i in 0 until rootView.childCount) { + stopAnimating(rootView.getChildAt(i)) + } + } + } + + private fun createListener( + bounds: Set<Bound>, + interpolator: Interpolator, + duration: Long, + ephemeral: Boolean + ): View.OnLayoutChangeListener { + return object : View.OnLayoutChangeListener { + override fun onLayoutChange( + view: View?, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + if (view == null) return + + val startLeft = getBound(view, Bound.LEFT) ?: oldLeft + val startTop = getBound(view, Bound.TOP) ?: oldTop + val startRight = getBound(view, Bound.RIGHT) ?: oldRight + val startBottom = getBound(view, Bound.BOTTOM) ?: oldBottom + + (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel() + + if (view.visibility == View.GONE || view.visibility == View.INVISIBLE) { + setBound(view, Bound.LEFT, left) + setBound(view, Bound.TOP, top) + setBound(view, Bound.RIGHT, right) + setBound(view, Bound.BOTTOM, bottom) + return + } + + val startValues = mapOf( + Bound.LEFT to startLeft, + Bound.TOP to startTop, + Bound.RIGHT to startRight, + Bound.BOTTOM to startBottom + ) + val endValues = mapOf( + Bound.LEFT to left, + Bound.TOP to top, + Bound.RIGHT to right, + Bound.BOTTOM to bottom + ) + + val boundsToAnimate = bounds.toMutableSet() + if (left == startLeft) boundsToAnimate.remove(Bound.LEFT) + if (top == startTop) boundsToAnimate.remove(Bound.TOP) + if (right == startRight) boundsToAnimate.remove(Bound.RIGHT) + if (bottom == startBottom) boundsToAnimate.remove(Bound.BOTTOM) + + if (boundsToAnimate.isNotEmpty()) { + startAnimation( + view, + boundsToAnimate, + startValues, + endValues, + interpolator, + duration, + ephemeral + ) + } + } + } + } + + private fun recursivelyAddListener(view: View, listener: View.OnLayoutChangeListener) { + // Make sure that only one listener is active at a time. + val oldListener = view.getTag(R.id.tag_layout_listener) + if (oldListener != null && oldListener is View.OnLayoutChangeListener) { + view.removeOnLayoutChangeListener(oldListener) + } + + view.addOnLayoutChangeListener(listener) + view.setTag(R.id.tag_layout_listener, listener) + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + recursivelyAddListener(view.getChildAt(i), listener) + } + } + } + + private fun getBound(view: View, bound: Bound): Int? { + return view.getTag(bound.overrideTag) as? Int + } + + private fun setBound(view: View, bound: Bound, value: Int) { + view.setTag(bound.overrideTag, value) + bound.setValue(view, value) + } + + /** + * Initiates the animation of a single bound by creating the animator, registering it with + * the [view], and starting it. If [ephemeral], the layout change listener is unregistered + * at the end of the animation, so no more animations happen. + */ + private fun startAnimation( + view: View, + bounds: Set<Bound>, + startValues: Map<Bound, Int>, + endValues: Map<Bound, Int>, + interpolator: Interpolator, + duration: Long, + ephemeral: Boolean + ) { + val propertyValuesHolders = buildList { + bounds.forEach { bound -> + add( + PropertyValuesHolder.ofInt( + PROPERTIES[bound], + startValues.getValue(bound), + endValues.getValue(bound) + ) + ) + } + }.toTypedArray() + + val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders) + animator.interpolator = interpolator + animator.duration = duration + animator.addListener(object : AnimatorListenerAdapter() { + var cancelled = false + + override fun onAnimationEnd(animation: Animator) { + view.setTag(R.id.tag_animator, null /* tag */) + bounds.forEach { view.setTag(it.overrideTag, null /* tag */) } + + // When an animation is cancelled, a new one might be taking over. We shouldn't + // unregister the listener yet. + if (ephemeral && !cancelled) { + val listener = view.getTag(R.id.tag_layout_listener) + if (listener != null && listener is View.OnLayoutChangeListener) { + view.setTag(R.id.tag_layout_listener, null /* tag */) + view.removeOnLayoutChangeListener(listener) + } + } + } + + override fun onAnimationCancel(animation: Animator?) { + cancelled = true + } + }) + + bounds.forEach { bound -> setBound(view, bound, startValues.getValue(bound)) } + + view.setTag(R.id.tag_animator, animator) + animator.start() + } + } +} diff --git a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml index b96c07ea53f0..07642736869c 100644 --- a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml +++ b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml @@ -15,10 +15,11 @@ ~ limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:color="?attr/wallpaperTextColorSecondary"> <item android:id="@android:id/background"> <shape android:shape="rectangle"> - <solid android:color="?android:attr/colorAccent"/> + <solid android:color="?androidprv:attr/colorAccentTertiary"/> <corners android:radius="24dp"/> </shape> </item> diff --git a/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml b/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml new file mode 100644 index 000000000000..9e61236aa7df --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.systemui.media.SquigglyProgress />
\ No newline at end of file diff --git a/packages/SystemUI/res/color/kg_user_avatar_frame.xml b/packages/SystemUI/res/color/kg_user_avatar_frame.xml index 174981e2a660..a143194f331d 100644 --- a/packages/SystemUI/res/color/kg_user_avatar_frame.xml +++ b/packages/SystemUI/res/color/kg_user_avatar_frame.xml @@ -18,6 +18,6 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_activated="true" - android:color="@color/kg_user_switcher_avatar_background" /> - <item android:color="@color/kg_user_switcher_avatar_background" /> + android:color="@color/user_avatar_color_bg" /> + <item android:color="@color/user_avatar_color_bg" /> </selector> diff --git a/packages/SystemUI/res/drawable/user_switcher_icon_large.xml b/packages/SystemUI/res/drawable/user_switcher_icon_large.xml index b78b2216c9f9..1ed75537059a 100644 --- a/packages/SystemUI/res/drawable/user_switcher_icon_large.xml +++ b/packages/SystemUI/res/drawable/user_switcher_icon_large.xml @@ -36,7 +36,7 @@ </item> <!-- Where the user drawable/bitmap will be placed --> <item - android:drawable="@drawable/kg_bg_avatar" + android:drawable="@drawable/user_avatar_bg" android:width="@dimen/bouncer_user_switcher_icon_size" android:height="@dimen/bouncer_user_switcher_icon_size" android:top="@dimen/user_switcher_icon_large_margin" diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index c58c00114262..27823009459c 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -26,7 +26,7 @@ android:layout_width="match_parent" android:layout_gravity="bottom" android:src="@drawable/overlay_actions_background_protection"/> - <com.android.systemui.clipboardoverlay.DraggableConstraintLayout + <com.android.systemui.screenshot.DraggableConstraintLayout android:id="@+id/clipboard_ui" android:theme="@style/FloatingOverlay" android:layout_width="match_parent" @@ -146,5 +146,5 @@ android:layout_margin="@dimen/overlay_dismiss_button_margin" android:src="@drawable/overlay_cancel"/> </FrameLayout> - </com.android.systemui.clipboardoverlay.DraggableConstraintLayout> + </com.android.systemui.screenshot.DraggableConstraintLayout> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 978998d6ca2c..f030f3130be4 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -44,6 +44,7 @@ android:background="@drawable/qs_media_scrim" /> + <!-- Guideline for output switcher --> <androidx.constraintlayout.widget.Guideline android:id="@+id/center_vertical_guideline" android:layout_width="wrap_content" @@ -51,6 +52,14 @@ android:orientation="vertical" app:layout_constraintGuide_percent="0.6" /> + <!-- Guideline for action buttons (collapsed view only) --> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/action_button_guideline" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_end="@dimen/qs_media_session_collapsed_guideline" /> + <!-- App icon --> <com.android.internal.widget.CachingIconView android:id="@+id/icon" @@ -118,15 +127,7 @@ android:singleLine="true" android:textSize="16sp" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="20dp" - android:layout_marginStart="@dimen/qs_media_padding" - android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constrainedWidth="true" - app:layout_constraintTop_toBottomOf="@id/icon" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/actionPlayPause" - app:layout_constraintHorizontal_bias="0" /> + android:layout_height="wrap_content" /> <!-- Artist name --> <TextView @@ -136,15 +137,7 @@ style="@style/MediaPlayer.Subtitle" android:textSize="14sp" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constrainedWidth="true" - android:layout_marginTop="1dp" - app:layout_constraintTop_toBottomOf="@id/header_title" - app:layout_constraintStart_toStartOf="@id/header_title" - app:layout_constraintEnd_toStartOf="@id/actionPlayPause" - app:layout_constraintBottom_toBottomOf="@id/actionPlayPause" - app:layout_constraintHorizontal_bias="0" /> + android:layout_height="wrap_content" /> <ImageButton android:id="@+id/actionPlayPause" @@ -153,9 +146,33 @@ android:layout_height="48dp" android:layout_marginStart="@dimen/qs_media_padding" android:layout_marginEnd="@dimen/qs_media_padding" - android:layout_marginTop="0dp" - android:layout_marginBottom="0dp" /> + /> + + <!-- See comment in media_session_collapsed.xml for how these barriers are used --> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/media_action_barrier" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintTop_toBottomOf="@id/header_title" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:barrierDirection="start" + app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" + /> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/media_action_barrier_end" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintTop_toBottomOf="@id/media_seamless" + app:layout_constraintBottom_toBottomOf="parent" + app:barrierDirection="end" + app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4" + app:layout_constraintStart_toStartOf="parent" + /> + <!-- Button visibility will be controlled in code --> <ImageButton android:id="@+id/actionPrev" style="@style/MediaPlayer.SessionAction" @@ -163,10 +180,9 @@ android:layout_height="48dp" android:layout_marginStart="4dp" android:layout_marginEnd="0dp" - android:layout_marginBottom="0dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" - app:layout_constraintHorizontal_bias="1" - app:layout_constraintHorizontal_chainStyle="packed" /> + /> <!-- Seek Bar --> <!-- As per Material Design on Bidirectionality, this is forced to LTR in code --> @@ -174,12 +190,12 @@ android:id="@+id/media_progress_bar" style="@style/MediaPlayer.ProgressBar" android:layout_width="0dp" - android:layout_height="wrap_content" + android:layout_height="48dp" android:paddingTop="@dimen/qs_media_session_enabled_seekbar_vertical_padding" android:paddingBottom="12dp" android:maxHeight="@dimen/qs_media_enabled_seekbar_height" android:splitTrack="false" - android:layout_marginBottom="0dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" android:layout_marginStart="0dp" android:layout_marginEnd="0dp" /> @@ -191,29 +207,58 @@ android:layout_height="48dp" android:layout_marginStart="0dp" android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="0dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" /> <ImageButton - android:id="@+id/actionStart" + android:id="@+id/action0" style="@style/MediaPlayer.SessionAction" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginEnd="@dimen/qs_media_action_spacing" - android:layout_marginBottom="0dp" + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp"/> + + <ImageButton + android:id="@+id/action1" + style="@style/MediaPlayer.SessionAction" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginStart="@dimen/qs_media_action_spacing" + android:layout_marginEnd="4dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:layout_marginTop="0dp" /> <ImageButton - android:id="@+id/actionEnd" + android:id="@+id/action2" style="@style/MediaPlayer.SessionAction" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginEnd="4dp" - android:layout_marginBottom="0dp" - android:layout_marginTop="0dp" - app:layout_constraintHorizontal_chainStyle="packed" /> + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" /> + + <ImageButton + android:id="@+id/action3" + style="@style/MediaPlayer.SessionAction" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginStart="@dimen/qs_media_action_spacing" + android:layout_marginEnd="4dp" + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" /> + + <ImageButton + android:id="@+id/action4" + style="@style/MediaPlayer.SessionAction" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginStart="@dimen/qs_media_action_spacing" + android:layout_marginEnd="4dp" + android:layout_marginBottom="@dimen/qs_media_padding" + android:layout_marginTop="0dp" /> <!-- Long press menu --> <TextView diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml index 813bb6018801..8de80844d784 100644 --- a/packages/SystemUI/res/layout/screenshot_static.xml +++ b/packages/SystemUI/res/layout/screenshot_static.xml @@ -14,25 +14,25 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<androidx.constraintlayout.widget.ConstraintLayout +<com.android.systemui.screenshot.DraggableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView - android:id="@+id/screenshot_actions_container_background" + android:id="@+id/actions_container_background" android:visibility="gone" android:layout_height="0dp" android:layout_width="0dp" android:elevation="1dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/screenshot_actions_container" + app:layout_constraintBottom_toBottomOf="@+id/actions_container" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/screenshot_actions_container" - app:layout_constraintEnd_toEndOf="@+id/screenshot_actions_container"/> + app:layout_constraintTop_toTopOf="@+id/actions_container" + app:layout_constraintEnd_toEndOf="@+id/actions_container"/> <HorizontalScrollView - android:id="@+id/screenshot_actions_container" + android:id="@+id/actions_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" @@ -130,4 +130,4 @@ app:layout_constraintStart_toStartOf="@id/screenshot_preview" app:layout_constraintTop_toTopOf="@id/screenshot_preview" android:elevation="@dimen/overlay_preview_elevation"/> -</androidx.constraintlayout.widget.ConstraintLayout> +</com.android.systemui.screenshot.DraggableConstraintLayout> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index 117404bad46e..c56ba7b6b1ce 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -22,6 +22,8 @@ <dimen name="ambient_indication_margin_bottom">115dp</dimen> <dimen name="lock_icon_margin_bottom">60dp</dimen> + <dimen name="qs_media_session_height_expanded">172dp</dimen> + <!-- margin from keyguard status bar to clock. For split shade it should be keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp --> <dimen name="keyguard_clock_top_margin">8dp</dimen> @@ -34,6 +36,8 @@ <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item> <dimen name="controls_task_view_right_margin">8dp</dimen> + <dimen name="split_shade_header_height">42dp</dimen> + <!-- Distance that the full shade transition takes in order to complete by tapping on a button like "expand". --> <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen> @@ -74,5 +78,5 @@ whether the progress is > 0, therefore this value is not very important. --> <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> - <dimen name="notification_panel_margin_horizontal">12dp</dimen> + <dimen name="notification_panel_margin_horizontal">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml index c9906053b4cb..f5c0509dbf5b 100644 --- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> <resources> - <dimen name="notification_panel_margin_horizontal">60dp</dimen> + <dimen name="notification_panel_margin_horizontal">48dp</dimen> <dimen name="status_view_margin_horizontal">62dp</dimen> <dimen name="keyguard_clock_top_margin">40dp</dimen> <dimen name="keyguard_status_view_bottom_margin">40dp</dimen> diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml index 71c195896051..f267088ea203 100644 --- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml @@ -23,5 +23,7 @@ <dimen name="keyguard_split_shade_top_margin">72dp</dimen> - <dimen name="notification_panel_margin_horizontal">24dp</dimen> + <dimen name="split_shade_header_height">56dp</dimen> + + <dimen name="qs_media_session_height_expanded">184dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml index e5f502f95290..44f8f3aee9ab 100644 --- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml @@ -21,8 +21,11 @@ for different hardware and product builds. --> <resources> <dimen name="status_view_margin_horizontal">124dp</dimen> - <dimen name="notification_panel_margin_horizontal">120dp</dimen> <dimen name="keyguard_clock_top_margin">80dp</dimen> <dimen name="keyguard_status_view_bottom_margin">80dp</dimen> <dimen name="bouncer_user_switcher_y_trans">90dp</dimen> + + <dimen name="notification_panel_margin_horizontal">96dp</dimen> + <dimen name="notification_side_paddings">40dp</dimen> + <dimen name="notification_section_divider_height">16dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index dc7470081da2..1edaaadfb433 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -52,7 +52,7 @@ (e.g. cannot be switched to) --> <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color> <!-- Color of background circle of user avatars in keyguard user switcher --> - <color name="kg_user_switcher_avatar_background">?android:attr/colorBackgroundFloating</color> + <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color> <!-- Icon color for user avatars in user switcher quick settings --> <color name="qs_user_switcher_avatar_icon_color">#3C4043</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5ca7285d3f73..52ec51629116 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -139,7 +139,7 @@ <!-- Height of a heads up notification in the status bar --> <dimen name="notification_max_heads_up_height_increased">188dp</dimen> - <!-- Side padding on the lockscreen on the side of notifications --> + <!-- Side padding on the side of notifications --> <dimen name="notification_side_paddings">16dp</dimen> <!-- padding between the heads up and the statusbar --> @@ -979,6 +979,11 @@ <dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen> <dimen name="qs_media_session_height_expanded">184dp</dimen> <dimen name="qs_media_session_height_collapsed">128dp</dimen> + <dimen name="qs_media_seekbar_progress_wavelength">20dp</dimen> + <dimen name="qs_media_seekbar_progress_amplitude">1.5dp</dimen> + <dimen name="qs_media_seekbar_progress_phase">8dp</dimen> + <dimen name="qs_media_seekbar_progress_stroke_width">2dp</dimen> + <dimen name="qs_media_session_collapsed_guideline">144dp</dimen> <!-- Size of Smartspace media recommendations cards in the QSPanel carousel --> <dimen name="qs_aa_media_rec_album_size_collapsed">72dp</dimen> @@ -1362,9 +1367,6 @@ <dimen name="dream_overlay_status_icon_margin">8dp</dimen> <dimen name="dream_overlay_status_bar_icon_size"> @*android:dimen/status_bar_system_icon_size</dimen> - <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications - shade. --> - <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen> <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen> <dimen name="dream_overlay_notification_indicator_size">6dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index f2eaa75496e8..3ae21e08005d 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -590,6 +590,7 @@ <style name="MediaPlayer.ProgressBar" parent="@android:style/Widget.ProgressBar.Horizontal"> <item name="android:thumbTint">?android:attr/textColorPrimary</item> + <item name="android:progressDrawable">@drawable/media_squiggly_progress</item> <item name="android:progressTint">?android:attr/textColorPrimary</item> <item name="android:progressBackgroundTint">?android:attr/textColorTertiary</item> <item name="android:clickable">true</item> diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index c6e18a6f8740..f00e03116337 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -19,6 +19,13 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> <Constraint + android:id="@+id/media_action_barrier" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/media_seamless" + app:layout_constraintStart_toEndOf="@id/action_button_guideline" /> + + <Constraint android:id="@+id/album_art" android:layout_width="match_parent" android:layout_height="@dimen/qs_media_session_height_collapsed" @@ -28,33 +35,73 @@ app:layout_constraintBottom_toBottomOf="parent" /> <Constraint + android:id="@+id/header_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:layout_marginStart="@dimen/qs_media_padding" + android:layout_marginEnd="@dimen/qs_media_padding" + app:layout_constraintEnd_toStartOf="@id/action_button_guideline" + app:layout_constrainedWidth="true" + app:layout_constraintTop_toBottomOf="@id/icon" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintHorizontal_bias="0" /> + <Constraint + android:id="@+id/header_artist" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/qs_media_padding" + android:layout_marginTop="0dp" + app:layout_constraintEnd_toStartOf="@id/action_button_guideline" + app:layout_constrainedWidth="true" + app:layout_constraintTop_toBottomOf="@id/header_title" + app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintVertical_bias="0" + app:layout_constraintHorizontal_bias="0" /> + + <Constraint android:id="@+id/actionPlayPause" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginEnd="@dimen/qs_media_padding" - app:layout_constraintStart_toEndOf="@id/actionEnd" + android:layout_marginBottom="@dimen/qs_media_padding" + app:layout_constraintVertical_bias="1" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" - app:layout_constraintBottom_toBottomOf="parent" /> + app:layout_constraintStart_toEndOf="@id/media_action_barrier_end" /> + <!-- + There will only be 3 action buttons shown at most in this layout (controlled in code). + Play/Pause should always be at the end, but the other buttons should remain in the same order + when in RTL. + This is accomplished by setting two barriers at the start and end of the small buttons, + anchored to a guideline set at 3x button width from the end. The text and play/pause button are + positioned relative to the barriers, and the small buttons use right/left constraints to stay + in the correct order inside the barriers. + --> <Constraint android:id="@+id/actionPrev" android:layout_width="48dp" android:layout_height="48dp" + android:layout_marginBottom="@dimen/qs_media_padding" app:layout_constraintHorizontal_bias="1" + app:layout_constraintVertical_bias="1" app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintStart_toEndOf="@id/header_artist" - app:layout_constraintEnd_toStartOf="@id/media_progress_bar" + app:layout_constraintRight_toLeftOf="@id/media_progress_bar" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/media_seamless" /> + app:layout_constraintTop_toBottomOf="@id/media_seamless" + app:layout_constraintLeft_toRightOf="@id/media_action_barrier" /> <Constraint android:id="@+id/media_progress_bar" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/qs_media_padding" android:visibility="gone" - app:layout_constraintStart_toEndOf="@id/actionPrev" - app:layout_constraintEnd_toStartOf="@id/actionNext" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/actionPrev" + app:layout_constraintRight_toLeftOf="@id/actionNext" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" /> @@ -62,29 +109,70 @@ android:id="@+id/actionNext" android:layout_width="48dp" android:layout_height="48dp" - app:layout_constraintStart_toEndOf="@id/media_progress_bar" - app:layout_constraintEnd_toStartOf="@id/actionStart" + android:layout_marginBottom="@dimen/qs_media_padding" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/media_progress_bar" + app:layout_constraintRight_toLeftOf="@id/action0" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/media_seamless" /> + + <Constraint + android:id="@+id/action0" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginBottom="@dimen/qs_media_padding" + android:visibility="gone" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/actionNext" + app:layout_constraintRight_toLeftOf="@id/action1" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/media_seamless" /> + + <Constraint + android:id="@+id/action1" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginBottom="@dimen/qs_media_padding" + android:visibility="gone" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/action0" + app:layout_constraintRight_toLeftOf="@id/action2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" /> <Constraint - android:id="@+id/actionStart" + android:id="@+id/action2" android:layout_width="48dp" android:layout_height="48dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:visibility="gone" - app:layout_constraintStart_toEndOf="@id/actionNext" - app:layout_constraintEnd_toStartOf="@id/actionEnd" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/action1" + app:layout_constraintRight_toLeftOf="@id/action3" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" /> <Constraint - android:id="@+id/actionEnd" + android:id="@+id/action3" android:layout_width="48dp" android:layout_height="48dp" + android:layout_marginBottom="@dimen/qs_media_padding" android:visibility="gone" - app:layout_constraintStart_toEndOf="@id/actionStart" - app:layout_constraintEnd_toStartOf="@id/actionPlayPause" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/action2" + app:layout_constraintRight_toLeftOf="@id/action4" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" /> -</ConstraintSet>
\ No newline at end of file + <Constraint + android:id="@+id/action4" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginBottom="@dimen/qs_media_padding" + android:visibility="gone" + app:layout_constraintVertical_bias="1" + app:layout_constraintLeft_toRightOf="@id/action3" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/media_seamless" + app:layout_constraintRight_toLeftOf="@id/media_action_barrier_end" /> +</ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml index 18ec7aa4cab4..10da70447ec0 100644 --- a/packages/SystemUI/res/xml/media_session_expanded.xml +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -28,57 +28,118 @@ app:layout_constraintBottom_toBottomOf="parent" /> <Constraint + android:id="@+id/header_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:layout_marginStart="@dimen/qs_media_padding" + android:layout_marginEnd="@dimen/qs_media_padding" + app:layout_constraintEnd_toStartOf="@id/actionPlayPause" + app:layout_constrainedWidth="true" + app:layout_constraintTop_toBottomOf="@id/icon" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintHorizontal_bias="0" /> + <Constraint + android:id="@+id/header_artist" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/qs_media_padding" + android:layout_marginTop="0dp" + app:layout_constrainedWidth="true" + app:layout_constraintEnd_toStartOf="@id/actionPlayPause" + app:layout_constraintBottom_toTopOf="@id/media_action_barrier" + app:layout_constraintTop_toBottomOf="@id/header_title" + app:layout_constraintStart_toStartOf="@id/header_title" + app:layout_constraintVertical_bias="0" + app:layout_constraintHorizontal_bias="0" /> + + <Constraint android:id="@+id/actionPlayPause" android:layout_width="48dp" android:layout_height="48dp" + android:layout_marginStart="@dimen/qs_media_padding" android:layout_marginEnd="@dimen/qs_media_padding" + android:layout_marginBottom="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/media_seamless" - app:layout_constraintBottom_toTopOf="@id/actionEnd" /> + app:layout_constraintBottom_toBottomOf="@id/header_artist" /> + <!-- + The bottom row of action buttons should remain in the same order when RTL, so their constraints + are set with right/left instead of start/end. + The chain is set to "spread" so that the progress bar can be weighted to fill any empty space. + --> <Constraint android:id="@+id/actionPrev" android:layout_width="48dp" android:layout_height="48dp" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/media_progress_bar" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toLeftOf="@id/media_progress_bar" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/actionPlayPause" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" + app:layout_constraintHorizontal_chainStyle="spread" /> <Constraint android:id="@+id/media_progress_bar" android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintStart_toEndOf="@id/actionPrev" - app:layout_constraintEnd_toStartOf="@id/actionNext" + android:layout_height="48dp" + app:layout_constraintLeft_toRightOf="@id/actionPrev" + app:layout_constraintRight_toLeftOf="@id/actionNext" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/actionPlayPause" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" + app:layout_constraintHorizontal_weight="1" /> <Constraint android:id="@+id/actionNext" android:layout_width="48dp" android:layout_height="48dp" - app:layout_constraintStart_toEndOf="@id/media_progress_bar" - app:layout_constraintEnd_toStartOf="@id/actionStart" + app:layout_constraintLeft_toRightOf="@id/media_progress_bar" + app:layout_constraintRight_toLeftOf="@id/action0" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/actionPlayPause" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" /> <Constraint - android:id="@+id/actionStart" + android:id="@+id/action0" android:layout_width="48dp" android:layout_height="48dp" - app:layout_constraintStart_toEndOf="@id/actionNext" - app:layout_constraintEnd_toStartOf="@id/actionEnd" + app:layout_constraintLeft_toRightOf="@id/actionNext" + app:layout_constraintRight_toLeftOf="@id/action1" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/actionPlayPause" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" /> <Constraint - android:id="@+id/actionEnd" + android:id="@+id/action1" android:layout_width="48dp" android:layout_height="48dp" - app:layout_constraintStart_toEndOf="@id/actionStart" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintLeft_toRightOf="@id/action0" + app:layout_constraintRight_toLeftOf="@id/action2" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/header_artist" /> + + <Constraint + android:id="@+id/action2" + android:layout_width="48dp" + android:layout_height="48dp" + app:layout_constraintLeft_toRightOf="@id/action1" + app:layout_constraintRight_toLeftOf="@id/action3" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@id/actionPlayPause" /> + app:layout_constraintTop_toBottomOf="@id/header_artist" /> -</ConstraintSet>
\ No newline at end of file + <Constraint + android:id="@+id/action3" + android:layout_width="48dp" + android:layout_height="48dp" + app:layout_constraintLeft_toRightOf="@id/action2" + app:layout_constraintRight_toLeftOf="@id/action4" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/header_artist" /> + + <Constraint + android:id="@+id/action4" + android:layout_width="48dp" + android:layout_height="48dp" + app:layout_constraintLeft_toRightOf="@id/action3" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/header_artist" /> +</ConstraintSet> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java deleted file mode 100644 index 325bcfc622d7..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.recents.utilities; - -import android.graphics.Bitmap; -import android.graphics.ColorSpace; -import android.graphics.ParcelableColorSpace; -import android.hardware.HardwareBuffer; -import android.os.Bundle; - -import java.util.Objects; - -/** - * Utils for working with Bitmaps. - */ -public final class BitmapUtil { - private static final String KEY_BUFFER = "bitmap_util_buffer"; - private static final String KEY_COLOR_SPACE = "bitmap_util_color_space"; - - private BitmapUtil(){ } - - /** - * Creates a Bundle that represents the given Bitmap. - * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid - * copies when passing across processes, only pass to processes you trust. - * - * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the - * returned Bundle should be treated as a standalone object. - * - * @param bitmap to convert to bundle - * @return a Bundle representing the bitmap, should only be parsed by - * {@link #bundleToHardwareBitmap(Bundle)} - */ - public static Bundle hardwareBitmapToBundle(Bitmap bitmap) { - if (bitmap.getConfig() != Bitmap.Config.HARDWARE) { - throw new IllegalArgumentException( - "Passed bitmap must have hardware config, found: " + bitmap.getConfig()); - } - - // Bitmap assumes SRGB for null color space - ParcelableColorSpace colorSpace = - bitmap.getColorSpace() == null - ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)) - : new ParcelableColorSpace(bitmap.getColorSpace()); - - Bundle bundle = new Bundle(); - bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer()); - bundle.putParcelable(KEY_COLOR_SPACE, colorSpace); - - return bundle; - } - - /** - * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .} - * - * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing this - * Bitmap on to any other source. - * - * @param bundle containing the bitmap - * @return a hardware Bitmap - */ - public static Bitmap bundleToHardwareBitmap(Bundle bundle) { - if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) { - throw new IllegalArgumentException("Bundle does not contain a hardware bitmap"); - } - - HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER); - ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE); - - return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer), - colorSpace.getColorSpace()); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt index 7b6791770295..8491f832b740 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt @@ -28,7 +28,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionPr * If the transition has already started by the moment when the clients are ready to play the * transition then it will report transition started callback and current animation progress. */ -class ScopedUnfoldTransitionProgressProvider +open class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor(source: UnfoldTransitionProgressProvider? = null) : UnfoldTransitionProgressProvider, TransitionProgressListener { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 7f456dba6053..eb418ff3b142 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -194,9 +194,12 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey mMessageAreaController.setMessage(mView.getWrongPasswordStringId()); } mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */); + startErrorAnimation(); } } + protected void startErrorAnimation() { /* no-op */ } + protected void verifyPasswordAndUnlock() { if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 1efda7edee2f..77044ed33113 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -188,14 +188,13 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { enableClipping(false); setTranslationY(0); - AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */, - mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator(), - getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR)); DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; - disappearAnimationUtils.startAnimation2d(mViews, - () -> { + disappearAnimationUtils.createAnimation( + this, 0, 200, mDisappearYTranslation, false, + mDisappearAnimationUtils.getInterpolator(), () -> { + getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR); enableClipping(true); if (finishRunnable != null) { finishRunnable.run(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 4723af2760c0..c46e33d9fd53 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -24,6 +24,9 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; @@ -32,6 +35,10 @@ import android.view.View; import com.android.internal.widget.LockscreenCredential; import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; + +import java.util.ArrayList; +import java.util.List; /** * A Pin based Keyguard input view @@ -188,4 +195,48 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView return getContext().getString( com.android.internal.R.string.keyguard_accessibility_pin_unlock); } + + /** + * Begins an error animation for this view. + **/ + public void startErrorAnimation() { + AnimatorSet animatorSet = new AnimatorSet(); + List<Animator> animators = new ArrayList(); + List<View> buttons = new ArrayList<>(); + for (int i = 1; i <= 9; i++) { + buttons.add(mButtons[i]); + } + buttons.add(mDeleteButton); + buttons.add(mButtons[0]); + buttons.add(mOkButton); + + int delay = 0; + for (int i = 0; i < buttons.size(); i++) { + final View button = buttons.get(i); + AnimatorSet animateWrapper = new AnimatorSet(); + animateWrapper.setStartDelay(delay); + + ValueAnimator scaleDownAnimator = ValueAnimator.ofFloat(1f, 0.8f); + scaleDownAnimator.setInterpolator(Interpolators.STANDARD); + scaleDownAnimator.addUpdateListener(valueAnimator -> { + button.setScaleX((float) valueAnimator.getAnimatedValue()); + button.setScaleY((float) valueAnimator.getAnimatedValue()); + }); + scaleDownAnimator.setDuration(50); + + ValueAnimator scaleUpAnimator = ValueAnimator.ofFloat(0.8f, 1f); + scaleUpAnimator.setInterpolator(Interpolators.STANDARD); + scaleUpAnimator.addUpdateListener(valueAnimator -> { + button.setScaleX((float) valueAnimator.getAnimatedValue()); + button.setScaleY((float) valueAnimator.getAnimatedValue()); + }); + scaleUpAnimator.setDuration(617); + + animateWrapper.playSequentially(scaleDownAnimator, scaleUpAnimator); + animators.add(animateWrapper); + delay += 33; + } + animatorSet.playTogether(animators); + animatorSet.start(); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index a9c03d1ac08e..cc7e4f7a0d9a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -134,4 +134,10 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB mView.setPasswordEntryEnabled(true); mMessageAreaController.setMessage(R.string.keyguard_enter_your_pin); } + + @Override + protected void startErrorAnimation() { + super.startErrorAnimation(); + mView.startErrorAnimation(); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index af7cf862b6b6..362fbed7055a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -918,7 +918,7 @@ public class KeyguardSecurityContainer extends FrameLayout { } drawable.setTint(iconColor); - Drawable bg = context.getDrawable(R.drawable.kg_bg_avatar); + Drawable bg = context.getDrawable(R.drawable.user_avatar_bg); bg.setTintBlendMode(BlendMode.DST); bg.setTint(Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurfaceVariant)); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 37f45644fa68..1ef6dea4e680 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -52,6 +52,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -752,15 +753,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private void handleFingerprintAcquired(int acquireInfo) { + private void handleFingerprintAcquired( + @BiometricFingerprintConstants.FingerprintAcquired int acquireInfo) { Assert.isMainThread(); - if (acquireInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) { - return; - } for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT); + cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT, acquireInfo); } } } @@ -960,14 +959,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFaceAcquired(int acquireInfo) { Assert.isMainThread(); - if (acquireInfo != FaceManager.FACE_ACQUIRED_GOOD) { - return; - } - if (DEBUG_FACE) Log.d(TAG, "Face acquired"); + if (DEBUG_FACE) Log.d(TAG, "Face acquired acquireInfo=" + acquireInfo); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricAcquired(BiometricSourceType.FACE); + cb.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 8d5603dc1563..ad2053cbc31b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -206,8 +206,10 @@ public class KeyguardUpdateMonitorCallback { * It is guaranteed that either {@link #onBiometricAuthenticated} or * {@link #onBiometricAuthFailed(BiometricSourceType)} is called after this method eventually. * @param biometricSourceType + * @param acquireInfo see {@link android.hardware.biometrics.BiometricFaceConstants} and + * {@link android.hardware.biometrics.BiometricFingerprintConstants} */ - public void onBiometricAcquired(BiometricSourceType biometricSourceType) { } + public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) { } /** * Called when a biometric couldn't be authenticated. diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index 00470c0efe35..f925eaa0e40b 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -73,12 +73,14 @@ class NumPadAnimator { ValueAnimator expandBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), mNormalColor, mHighlightColor); expandBackgroundColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS); + expandBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); expandBackgroundColorAnimator.addUpdateListener( animator -> mBackground.setColor((int) animator.getAnimatedValue())); ValueAnimator expandTextColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), textColorPrimary, textColorPrimaryInverse); + expandTextColorAnimator.setInterpolator(Interpolators.LINEAR); expandTextColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS); expandTextColorAnimator.addUpdateListener(valueAnimator -> { if (digitTextView != null) { @@ -98,6 +100,7 @@ class NumPadAnimator { anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue())); ValueAnimator contractBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), mHighlightColor, mNormalColor); + contractBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); contractBackgroundColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS); contractBackgroundColorAnimator.setDuration(CONTRACT_ANIMATION_MS); contractBackgroundColorAnimator.addUpdateListener( @@ -106,6 +109,7 @@ class NumPadAnimator { ValueAnimator contractTextColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), textColorPrimaryInverse, textColorPrimary); + contractTextColorAnimator.setInterpolator(Interpolators.LINEAR); contractTextColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS); contractTextColorAnimator.setDuration(CONTRACT_ANIMATION_MS); contractTextColorAnimator.addUpdateListener(valueAnimator -> { diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index e0d0fc25943b..7b98f2759d80 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -19,6 +19,8 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.VectorDrawable; import android.util.AttributeSet; import android.view.MotionEvent; @@ -37,8 +39,14 @@ public class NumPadButton extends AlphaOptimizedImageButton { public NumPadButton(Context context, AttributeSet attrs) { super(context, attrs); - mAnimator = new NumPadAnimator(context, getBackground().mutate(), - attrs.getStyleAttribute()); + Drawable background = getBackground(); + if (background instanceof GradientDrawable) { + mAnimator = new NumPadAnimator(context, background.mutate(), + attrs.getStyleAttribute()); + } else { + mAnimator = null; + } + } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index 88a0bcec0542..5cab547ee435 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -18,6 +18,8 @@ package com.android.keyguard; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.os.PowerManager; import android.os.SystemClock; import android.util.AttributeSet; @@ -129,8 +131,13 @@ public class NumPadKey extends ViewGroup { setContentDescription(mDigitText.getText().toString()); - mAnimator = new NumPadAnimator(context, getBackground().mutate(), - R.style.NumPadKey, mDigitText); + Drawable background = getBackground(); + if (background instanceof GradientDrawable) { + mAnimator = new NumPadAnimator(context, background.mutate(), + R.style.NumPadKey, mDigitText); + } else { + mAnimator = null; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java index bc2a1ff24235..7afd43d1cb06 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricSourceType; import android.os.Build; @@ -78,7 +79,8 @@ public class LatencyTester extends CoreStartable { } private void fakeWakeAndUnlock(BiometricSourceType type) { - mBiometricUnlockController.onBiometricAcquired(type); + mBiometricUnlockController.onBiometricAcquired(type, + BiometricConstants.BIOMETRIC_ACQUIRED_GOOD); mBiometricUnlockController.onBiometricAuthenticated( KeyguardUpdateMonitor.getCurrentUser(), type, true /* isStrongBiometric */); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 50ca447090b5..7e1a02626dc9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -1072,6 +1072,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold pw.println("WindowMagnificationController (displayId=" + mDisplayId + "):"); pw.println(" mOverlapWithGestureInsets:" + mOverlapWithGestureInsets); pw.println(" mScale:" + mScale); + pw.println(" mWindowBounds:" + mWindowBounds); pw.println(" mMirrorViewBounds:" + (isWindowVisible() ? mMirrorViewBounds : "empty")); pw.println(" mMagnificationFrameBoundary:" + (isWindowVisible() ? mMagnificationFrameBoundary : "empty")); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 99f27d7f48e7..a27b9cd357f4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.graphics.PointF +import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricSourceType import android.util.DisplayMetrics import android.util.Log @@ -39,8 +40,8 @@ import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -257,6 +258,16 @@ class AuthRippleController @Inject constructor( override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) { mView.retractRipple() } + + override fun onBiometricAcquired( + biometricSourceType: BiometricSourceType?, + acquireInfo: Int + ) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT && + acquireInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_PARTIAL) { + mView.retractRipple() + } + } } private val configurationChangedListener = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 8052c2071d86..bf42db53d3d8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -16,6 +16,7 @@ package com.android.systemui.biometrics; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD; import static com.android.internal.util.Preconditions.checkArgument; @@ -30,6 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.Point; import android.graphics.RectF; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.SensorLocationInternal; import android.hardware.display.DisplayManager; import android.hardware.fingerprint.FingerprintManager; @@ -141,11 +143,11 @@ public class UdfpsController implements DozeReceiver { private int mActivePointerId = -1; // The timestamp of the most recent touch log. private long mTouchLogTime; - // Sensor has a good capture for this touch. Do not need to illuminate for this particular - // touch event anymore. In other words, do not illuminate until user lifts and touches the - // sensor area again. + // Sensor has a capture (good or bad) for this touch. Do not need to illuminate for this + // particular touch event anymore. In other words, do not illuminate until user lifts and + // touches the sensor area again. // TODO: We should probably try to make touch/illumination things more of a FSM - private boolean mGoodCaptureReceived; + private boolean mAcquiredReceived; // The current request from FingerprintService. Null if no current request. @Nullable UdfpsControllerOverlay mOverlay; @@ -221,19 +223,28 @@ public class UdfpsController implements DozeReceiver { } @Override - public void onAcquiredGood(int sensorId) { - mFgExecutor.execute(() -> { - if (mOverlay == null) { - Log.e(TAG, "Null request when onAcquiredGood for sensorId: " + sensorId); - return; - } - mGoodCaptureReceived = true; - final UdfpsView view = mOverlay.getOverlayView(); - if (view != null) { - view.stopIllumination(); - } - mOverlay.onAcquiredGood(); - }); + public void onAcquired( + int sensorId, + @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo + ) { + if (BiometricFingerprintConstants.shouldTurnOffHbm(acquiredInfo)) { + boolean acquiredGood = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD; + mFgExecutor.execute(() -> { + if (mOverlay == null) { + Log.e(TAG, "Null request when onAcquired for sensorId: " + sensorId + + " acquiredInfo=" + acquiredInfo); + return; + } + mAcquiredReceived = true; + final UdfpsView view = mOverlay.getOverlayView(); + if (view != null) { + view.stopIllumination(); // turn off HBM + } + if (acquiredGood) { + mOverlay.onAcquiredGood(); + } + }); + } } @Override @@ -414,8 +425,8 @@ public class UdfpsController implements DozeReceiver { "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", minor, major, v, exceedsVelocityThreshold); final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; - if (!isIlluminationRequested && !mGoodCaptureReceived && - !exceedsVelocityThreshold) { + if (!isIlluminationRequested && !mAcquiredReceived + && !exceedsVelocityThreshold) { final int rawX = (int) event.getRawX(); final int rawY = (int) event.getRawY(); // Default coordinates assume portrait mode. @@ -799,7 +810,7 @@ public class UdfpsController implements DozeReceiver { private void onFingerUp(@NonNull UdfpsView view) { mExecution.assertIsMainThread(); mActivePointerId = -1; - mGoodCaptureReceived = false; + mAcquiredReceived = false; if (mOnFingerDown) { mFingerprintManager.onPointerUp(mSensorProps.sensorId); for (Callback cb : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt new file mode 100644 index 000000000000..6615f6b0b9eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt @@ -0,0 +1,132 @@ +package com.android.systemui.broadcast + +import android.annotation.AnyThread +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.UserHandle +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.wakelock.WakeLock +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * SystemUI master Broadcast sender + * + * This class dispatches broadcasts on background thread to avoid synchronous call to binder. Use + * this class instead of calling [Context.sendBroadcast] directly. + */ +@SysUISingleton +class BroadcastSender @Inject constructor( + private val context: Context, + private val wakeLockBuilder: WakeLock.Builder, + @Background private val bgExecutor: Executor +) { + + private val WAKE_LOCK_TAG = "SysUI:BroadcastSender" + private val WAKE_LOCK_SEND_REASON = "sendInBackground" + + /** + * Sends broadcast via [Context.sendBroadcast] on background thread to avoid blocking + * synchronous binder call. + */ + @AnyThread + fun sendBroadcast(intent: Intent) { + sendInBackground { + context.sendBroadcast(intent) + } + } + + /** + * Sends broadcast via [Context.sendBroadcast] on background thread to avoid blocking + * synchronous binder call. + */ + @AnyThread + fun sendBroadcast(intent: Intent, receiverPermission: String?) { + sendInBackground { + context.sendBroadcast(intent, receiverPermission) + } + } + + /** + * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking + * synchronous binder call. + */ + @AnyThread + fun sendBroadcastAsUser(intent: Intent, userHandle: UserHandle) { + sendInBackground { + context.sendBroadcastAsUser(intent, userHandle) + } + } + + /** + * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking + * synchronous binder call. + */ + @AnyThread + fun sendBroadcastAsUser(intent: Intent, userHandle: UserHandle, receiverPermission: String?) { + sendInBackground { + context.sendBroadcastAsUser(intent, userHandle, receiverPermission) + } + } + + /** + * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking + * synchronous binder call. + */ + @AnyThread + fun sendBroadcastAsUser( + intent: Intent, + userHandle: UserHandle, + receiverPermission: String?, + options: Bundle? + ) { + sendInBackground { + context.sendBroadcastAsUser(intent, userHandle, receiverPermission, options) + } + } + + /** + * Sends broadcast via [Context.sendBroadcastAsUser] on background thread to avoid blocking + * synchronous binder call. + */ + @AnyThread + fun sendBroadcastAsUser( + intent: Intent, + userHandle: UserHandle, + receiverPermission: String?, + appOp: Int + ) { + sendInBackground { + context.sendBroadcastAsUser(intent, userHandle, receiverPermission, appOp) + } + } + + /** + * Sends [Intent.ACTION_CLOSE_SYSTEM_DIALOGS] broadcast to the system. + */ + @AnyThread + fun closeSystemDialogs() { + sendInBackground { + context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) + } + } + + /** + * Dispatches parameter on background executor while holding a wakelock. + */ + private fun sendInBackground(callable: () -> Unit) { + val broadcastWakelock = wakeLockBuilder.setTag(WAKE_LOCK_TAG) + .setMaxTimeout(5000) + .build() + broadcastWakelock.acquire(WAKE_LOCK_SEND_REASON) + bgExecutor.execute { + try { + callable.invoke() + } finally { + broadcastWakelock.release(WAKE_LOCK_SEND_REASON) + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 56e4d7ec1882..0f937d163f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -73,6 +73,7 @@ import android.widget.TextView; import com.android.internal.policy.PhoneWindow; import com.android.systemui.R; +import com.android.systemui.screenshot.DraggableConstraintLayout; import com.android.systemui.screenshot.FloatingWindowUtil; import com.android.systemui.screenshot.OverlayActionChip; import com.android.systemui.screenshot.TimeoutHandler; @@ -166,8 +167,28 @@ public class ClipboardOverlayController { mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip)); mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button)); - mView.setOnDismissEndCallback(this::hideImmediate); - mView.setOnInteractionCallback(mTimeoutHandler::resetTimeout); + mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() { + @Override + public void onInteraction() { + mTimeoutHandler.resetTimeout(); + } + + @Override + public void onSwipeDismissInitiated(Animator animator) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mContainer.animate().alpha(0).start(); + } + }); + } + + @Override + public void onDismissComplete() { + hideImmediate(); + } + }); mDismissButton.setOnClickListener(view -> animateOut()); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java deleted file mode 100644 index 57dc1620a410..000000000000 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java +++ /dev/null @@ -1,163 +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.systemui.clipboardoverlay; - -import android.animation.Animator; -import android.content.Context; -import android.graphics.Rect; -import android.graphics.Region; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewTreeObserver; - -import androidx.constraintlayout.widget.ConstraintLayout; - -import com.android.systemui.R; -import com.android.systemui.screenshot.SwipeDismissHandler; - -import java.util.function.Consumer; - -/** - * ConstraintLayout that is draggable when touched in a specific region - */ -public class DraggableConstraintLayout extends ConstraintLayout - implements ViewTreeObserver.OnComputeInternalInsetsListener { - private final SwipeDismissHandler mSwipeDismissHandler; - private final GestureDetector mSwipeDetector; - private Consumer<Animator> mOnDismissInitiated; - private Runnable mOnDismissComplete; - private Runnable mOnInteraction; - - public DraggableConstraintLayout(Context context) { - this(context, null); - } - - public DraggableConstraintLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - mSwipeDismissHandler = new SwipeDismissHandler(mContext, this, - new SwipeDismissHandler.SwipeDismissCallbacks() { - @Override - public void onInteraction() { - if (mOnInteraction != null) { - mOnInteraction.run(); - } - } - - @Override - public void onSwipeDismissInitiated(Animator animator) { - if (mOnDismissInitiated != null) { - mOnDismissInitiated.accept(animator); - } - } - - @Override - public void onDismissComplete() { - if (mOnDismissComplete != null) { - mOnDismissComplete.run(); - } - } - }); - setOnTouchListener(mSwipeDismissHandler); - - mSwipeDetector = new GestureDetector(mContext, - new GestureDetector.SimpleOnGestureListener() { - final Rect mActionsRect = new Rect(); - - @Override - public boolean onScroll( - MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { - View actionsContainer = findViewById(R.id.actions_container); - actionsContainer.getBoundsOnScreen(mActionsRect); - // return true if we aren't in the actions bar, or if we are but it isn't - // scrollable in the direction of movement - return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY()) - || !actionsContainer.canScrollHorizontally((int) distanceX); - } - }); - mSwipeDetector.setIsLongpressEnabled(false); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { - mSwipeDismissHandler.onTouch(this, ev); - } - - return mSwipeDetector.onTouchEvent(ev); - } - - /** - * Dismiss the view, with animation controlled by SwipeDismissHandler - */ - public void dismiss() { - mSwipeDismissHandler.dismiss(); - } - - /** - * Set the callback to be run after view is dismissed (before animation; receives animator as - * input) - */ - public void setOnDismissStartCallback(Consumer<Animator> callback) { - mOnDismissInitiated = callback; - } - - /** - * Set the callback to be run after view is dismissed - */ - public void setOnDismissEndCallback(Runnable callback) { - mOnDismissComplete = callback; - } - - /** - * Set the callback to be run when the view is interacted with (e.g. tapped) - */ - public void setOnInteractionCallback(Runnable callback) { - mOnInteraction = callback; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - getViewTreeObserver().addOnComputeInternalInsetsListener(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - } - - @Override - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { - // Only child views are touchable. - Region r = new Region(); - Rect rect = new Rect(); - for (int i = 0; i < getChildCount(); i++) { - getChildAt(i).getGlobalVisibleRect(rect); - r.op(rect, Region.Op.UNION); - } - inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - inoutInfo.touchableRegion.set(r); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 40662536e57e..f9115b20ca06 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -17,10 +17,13 @@ package com.android.systemui.controls.management import android.content.ComponentName +import android.content.res.Configuration +import android.content.res.Resources import android.graphics.Rect import android.os.Bundle import android.service.controls.Control import android.service.controls.DeviceTypes +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -32,7 +35,6 @@ import android.widget.TextView import androidx.core.view.AccessibilityDelegateCompat import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat -import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.controls.ControlInterface @@ -56,11 +58,32 @@ class ControlAdapter( const val TYPE_ZONE = 0 const val TYPE_CONTROL = 1 const val TYPE_DIVIDER = 2 - } - val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if (getItemViewType(position) != TYPE_CONTROL) 2 else 1 + /** + * For low-dp width screens that also employ an increased font scale, adjust the + * number of columns. This helps prevent text truncation on these devices. + * + */ + @JvmStatic + fun findMaxColumns(res: Resources): Int { + var maxColumns = res.getInteger(R.integer.controls_max_columns) + val maxColumnsAdjustWidth = + res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp) + + val outValue = TypedValue() + res.getValue(R.dimen.controls_max_columns_adjust_above_font_scale, outValue, true) + val maxColumnsAdjustFontScale = outValue.getFloat() + + val config = res.configuration + val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT + if (isPortrait && + config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED && + config.screenWidthDp <= maxColumnsAdjustWidth && + config.fontScale >= maxColumnsAdjustFontScale) { + maxColumns-- + } + + return maxColumns } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index 6f94943472b1..f611c3ef966d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -195,10 +195,11 @@ class ControlsEditingActivity @Inject constructor( val margin = resources .getDimensionPixelSize(R.dimen.controls_card_margin) val itemDecorator = MarginItemDecorator(margin, margin) + val spanCount = ControlAdapter.findMaxColumns(resources) recyclerView.apply { this.adapter = adapter - layoutManager = object : GridLayoutManager(recyclerView.context, 2) { + layoutManager = object : GridLayoutManager(recyclerView.context, spanCount) { // This will remove from the announcement the row corresponding to the divider, // as it's not something that should be announced. @@ -210,7 +211,12 @@ class ControlsEditingActivity @Inject constructor( return if (initial > 0) initial - 1 else initial } }.apply { - spanSizeLookup = adapter.spanSizeLookup + spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (adapter?.getItemViewType(position) + != ControlAdapter.TYPE_CONTROL) spanCount else 1 + } + } } addItemDecoration(itemDecorator) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt index cb67454195ec..747bcbe1c229 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -60,11 +60,17 @@ class StructureAdapter( val margin = itemView.context.resources .getDimensionPixelSize(R.dimen.controls_card_margin) val itemDecorator = MarginItemDecorator(margin, margin) + val spanCount = ControlAdapter.findMaxColumns(itemView.resources) recyclerView.apply { this.adapter = controlAdapter - layoutManager = GridLayoutManager(recyclerView.context, 2).apply { - spanSizeLookup = controlAdapter.spanSizeLookup + layoutManager = GridLayoutManager(recyclerView.context, spanCount).apply { + spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (adapter?.getItemViewType(position) + != ControlAdapter.TYPE_CONTROL) spanCount else 1 + } + } } addItemDecoration(itemDecorator) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 5c1d8c3929cb..e53f2673841c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -16,11 +16,11 @@ package com.android.systemui.controls.ui +import android.annotation.AnyThread import android.annotation.MainThread import android.app.Dialog import android.app.PendingIntent import android.content.Context -import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.database.ContentObserver @@ -35,6 +35,7 @@ import android.service.controls.actions.FloatAction import android.util.Log import android.view.HapticFeedbackConstants import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -53,6 +54,7 @@ class ControlActionCoordinatorImpl @Inject constructor( private val bgExecutor: DelayableExecutor, @Main private val uiExecutor: DelayableExecutor, private val activityStarter: ActivityStarter, + private val broadcastSender: BroadcastSender, private val keyguardStateController: KeyguardStateController, private val taskViewFactory: Optional<TaskViewFactory>, private val controlsMetricsLogger: ControlsMetricsLogger, @@ -199,11 +201,12 @@ class ControlActionCoordinatorImpl @Inject constructor( false } + @AnyThread @VisibleForTesting fun bouncerOrRun(action: Action, authRequired: Boolean) { if (keyguardStateController.isShowing() && authRequired) { if (isLocked) { - context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) + broadcastSender.closeSystemDialogs() // pending actions will only run after the control state has been refreshed pendingAction = action @@ -233,7 +236,10 @@ class ControlActionCoordinatorImpl @Inject constructor( // make sure the intent is valid before attempting to open the dialog if (activities.isNotEmpty() && taskViewFactory.isPresent) { taskViewFactory.get().create(context, uiExecutor, { - dialog = DetailDialog(activityContext, it, pendingIntent, cvh).also { + dialog = DetailDialog( + activityContext, broadcastSender, + it, pendingIntent, cvh + ).also { it.setOnDismissListener { _ -> dialog = null } it.show() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 59c291cf9e5c..1268250137b8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -25,12 +25,10 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.SharedPreferences -import android.content.res.Configuration import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.service.controls.Control import android.util.Log -import android.util.TypedValue import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View @@ -51,6 +49,7 @@ import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.management.ControlAdapter import com.android.systemui.controls.management.ControlsEditingActivity import com.android.systemui.controls.management.ControlsFavoritingActivity import com.android.systemui.controls.management.ControlsListingController @@ -386,7 +385,7 @@ class ControlsUiControllerImpl @Inject constructor ( visibility = View.VISIBLE } - val maxColumns = findMaxColumns() + val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources) val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup var lastRow: ViewGroup = createRow(inflater, listView) @@ -432,32 +431,6 @@ class ControlsUiControllerImpl @Inject constructor ( } } - /** - * For low-dp width screens that also employ an increased font scale, adjust the - * number of columns. This helps prevent text truncation on these devices. - */ - private fun findMaxColumns(): Int { - val res = activityContext.resources - var maxColumns = res.getInteger(R.integer.controls_max_columns) - val maxColumnsAdjustWidth = - res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp) - - val outValue = TypedValue() - res.getValue(R.dimen.controls_max_columns_adjust_above_font_scale, outValue, true) - val maxColumnsAdjustFontScale = outValue.getFloat() - - val config = res.configuration - val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT - if (isPortrait && - config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED && - config.screenWidthDp <= maxColumnsAdjustWidth && - config.fontScale >= maxColumnsAdjustFontScale) { - maxColumns-- - } - - return maxColumns - } - override fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo { if (structures.isEmpty()) return EMPTY_STRUCTURE diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index dc3d1b52495c..80589a2711cc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -33,6 +33,7 @@ import android.view.WindowManager import android.widget.ImageView import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastSender import com.android.wm.shell.TaskView /** @@ -42,6 +43,7 @@ import com.android.wm.shell.TaskView */ class DetailDialog( val activityContext: Context, + val broadcastSender: BroadcastSender, val taskView: TaskView, val pendingIntent: PendingIntent, val cvh: ControlViewHolder @@ -147,7 +149,7 @@ class DetailDialog( // startActivity() below is called. removeDetailTask() dismiss() - context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) + broadcastSender.closeSystemDialogs() pendingIntent.send() } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 8e323051012d..166c2654cbaa 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -48,6 +48,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.settings.dagger.SettingsModule; +import com.android.systemui.smartspace.dagger.SmartspaceModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -119,6 +120,7 @@ import dagger.Provides; SettingsModule.class, SettingsUtilModule.class, SmartRepliesInflationModule.class, + SmartspaceModule.class, StatusBarPolicyModule.class, StatusBarWindowModule.class, SysUIConcurrencyModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 69f15e692065..995df19f64c2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -23,8 +23,6 @@ import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.ComplicationHostViewController; import com.android.systemui.dreams.dagger.DreamOverlayComponent; @@ -39,9 +37,6 @@ import javax.inject.Named; */ @DreamOverlayComponent.DreamOverlayScope public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> { - // The height of the area at the top of the dream overlay to allow dragging down the - // notifications shade. - private final int mDreamOverlayNotificationsDragAreaHeight; private final DreamOverlayStatusBarViewController mStatusBarViewController; private final ComplicationHostViewController mComplicationHostViewController; @@ -79,9 +74,6 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; - mDreamOverlayNotificationsDragAreaHeight = - mView.getResources().getDimensionPixelSize( - R.dimen.dream_overlay_notifications_drag_area_height); mComplicationHostViewController = complicationHostViewController; final View view = mComplicationHostViewController.getView(); @@ -117,11 +109,6 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve return mView; } - @VisibleForTesting - int getDreamOverlayNotificationsDragAreaHeight() { - return mDreamOverlayNotificationsDragAreaHeight; - } - private void updateBurnInOffsets() { int burnInOffset = mMaxBurnInOffset; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index ebc766635733..dfbb0c7c1624 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -65,6 +65,9 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // A reference to the {@link Window} used to hold the dream overlay. private Window mWindow; + // True if the service has been destroyed. + private boolean mDestroyed; + private final Complication.Host mHost = new Complication.Host() { @Override public void requestExitDream() { @@ -134,6 +137,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mPreviewComplication.setDreamLabel(null); mStateController.removeComplication(mPreviewComplication); mStateController.setPreviewMode(false); + mDestroyed = true; super.onDestroy(); } @@ -141,6 +145,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { setCurrentState(Lifecycle.State.STARTED); mExecutor.execute(() -> { + if (mDestroyed) { + // The task could still be executed after the service has been destroyed. Bail if + // that is the case. + return; + } mStateController.setShouldShowComplications(shouldShowComplications()); mStateController.setPreviewMode(isPreviewMode()); if (isPreviewMode()) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java index a5dcd39264df..a83e006dfa2f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams; import android.content.Context; +import android.os.Parcelable; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -25,7 +26,10 @@ import com.android.systemui.CoreStartable; import com.android.systemui.dreams.complication.Complication; import com.android.systemui.dreams.complication.ComplicationLayoutParams; import com.android.systemui.dreams.complication.ComplicationViewModel; -import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; +import com.android.systemui.dreams.smartspace.DreamsSmartspaceController; +import com.android.systemui.plugins.BcSmartspaceDataPlugin; + +import java.util.List; import javax.inject.Inject; @@ -39,10 +43,22 @@ public class SmartSpaceComplication implements Complication { * SystemUI. */ public static class Registrant extends CoreStartable { - private final LockscreenSmartspaceController mSmartSpaceController; + private final DreamsSmartspaceController mSmartSpaceController; private final DreamOverlayStateController mDreamOverlayStateController; private final SmartSpaceComplication mComplication; + private final BcSmartspaceDataPlugin.SmartspaceTargetListener mSmartspaceListener = + new BcSmartspaceDataPlugin.SmartspaceTargetListener() { + @Override + public void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets) { + if (!targets.isEmpty()) { + mDreamOverlayStateController.addComplication(mComplication); + } else { + mDreamOverlayStateController.removeComplication(mComplication); + } + } + }; + /** * Default constructor for {@link SmartSpaceComplication}. */ @@ -50,7 +66,7 @@ public class SmartSpaceComplication implements Complication { public Registrant(Context context, DreamOverlayStateController dreamOverlayStateController, SmartSpaceComplication smartSpaceComplication, - LockscreenSmartspaceController smartSpaceController) { + DreamsSmartspaceController smartSpaceController) { super(context); mDreamOverlayStateController = dreamOverlayStateController; mComplication = smartSpaceComplication; @@ -59,32 +75,27 @@ public class SmartSpaceComplication implements Complication { @Override public void start() { - addOrRemoveOverlay(); mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() { @Override public void onStateChanged() { - addOrRemoveOverlay(); + if (mDreamOverlayStateController.isOverlayActive()) { + mSmartSpaceController.addListener(mSmartspaceListener); + } else { + mSmartSpaceController.removeListener(mSmartspaceListener); + } } }); } - - private void addOrRemoveOverlay() { - if (mDreamOverlayStateController.isPreviewMode()) { - mDreamOverlayStateController.removeComplication(mComplication); - } else if (mSmartSpaceController.isEnabled()) { - mDreamOverlayStateController.addComplication(mComplication); - } - } } private static class SmartSpaceComplicationViewHolder implements ViewHolder { private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10; - private final LockscreenSmartspaceController mSmartSpaceController; + private final DreamsSmartspaceController mSmartSpaceController; private final Context mContext; protected SmartSpaceComplicationViewHolder( Context context, - LockscreenSmartspaceController smartSpaceController) { + DreamsSmartspaceController smartSpaceController) { mSmartSpaceController = smartSpaceController; mContext = context; } @@ -109,12 +120,12 @@ public class SmartSpaceComplication implements Complication { } } - private final LockscreenSmartspaceController mSmartSpaceController; + private final DreamsSmartspaceController mSmartSpaceController; private final Context mContext; @Inject public SmartSpaceComplication(Context context, - LockscreenSmartspaceController smartSpaceController) { + DreamsSmartspaceController smartSpaceController) { mContext = context; mSmartSpaceController = smartSpaceController; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt new file mode 100644 index 000000000000..4e228a14fc88 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.smartspace + +import android.app.smartspace.SmartspaceConfig +import android.app.smartspace.SmartspaceManager +import android.app.smartspace.SmartspaceSession +import android.content.Context +import android.graphics.Color +import android.util.Log +import android.view.View +import android.view.ViewGroup +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView +import com.android.systemui.smartspace.SmartspacePrecondition +import com.android.systemui.smartspace.SmartspaceTargetFilter +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER +import com.android.systemui.smartspace.dagger.SmartspaceViewComponent +import com.android.systemui.util.concurrency.Execution +import java.lang.RuntimeException +import java.util.Optional +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Named + +/** + * Controller for managing the smartspace view on the dream + */ +@SysUISingleton +class DreamsSmartspaceController @Inject constructor( + private val context: Context, + private val smartspaceManager: SmartspaceManager, + private val execution: Execution, + @Main private val uiExecutor: Executor, + private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory, + @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition, + @Named(DREAM_SMARTSPACE_TARGET_FILTER) + private val optionalTargetFilter: Optional<SmartspaceTargetFilter>, + @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin> +) { + companion object { + private const val TAG = "DreamsSmartspaceCtrlr" + } + + private var session: SmartspaceSession? = null + private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) + private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null) + + // A shadow copy of listeners is maintained to track whether the session should remain open. + private var listeners = mutableSetOf<BcSmartspaceDataPlugin.SmartspaceTargetListener>() + + // Smartspace can be used on multiple displays, such as when the user casts their screen + private var smartspaceViews = mutableSetOf<SmartspaceView>() + + var preconditionListener = object : SmartspacePrecondition.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } + } + + init { + precondition.addListener(preconditionListener) + } + + var filterListener = object : SmartspaceTargetFilter.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } + } + + init { + targetFilter?.addListener(filterListener) + } + + var stateChangeListener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + val view = v as SmartspaceView + // Until there is dream color matching + view.setPrimaryTextColor(Color.WHITE) + smartspaceViews.add(view) + connectSession() + } + + override fun onViewDetachedFromWindow(v: View) { + smartspaceViews.remove(v as SmartspaceView) + + if (smartspaceViews.isEmpty()) { + disconnect() + } + } + } + + private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> + execution.assertIsMainThread() + + val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true } + plugin?.onTargetsAvailable(filteredTargets) + } + + /** + * Constructs the smartspace view and connects it to the smartspace service. + */ + fun buildAndConnectView(parent: ViewGroup): View? { + execution.assertIsMainThread() + + if (!precondition.conditionsMet()) { + throw RuntimeException("Cannot build view when not enabled") + } + + val view = buildView(parent) + connectSession() + + return view + } + + private fun buildView(parent: ViewGroup): View? { + return if (plugin != null) { + var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener) + .getView() + + if (view is View) { + return view + } + + return null + } else { + null + } + } + + private fun hasActiveSessionListeners(): Boolean { + return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() + } + + private fun connectSession() { + if (plugin == null || session != null || !hasActiveSessionListeners()) { + return + } + + if (!precondition.conditionsMet()) { + return + } + + // TODO(b/217559844): Replace with "dream" session when available. + val newSession = smartspaceManager.createSmartspaceSession( + SmartspaceConfig.Builder(context, "lockscreen").build()) + Log.d(TAG, "Starting smartspace session for dream") + newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) + this.session = newSession + + plugin.registerSmartspaceEventNotifier { + e -> session?.notifySmartspaceEvent(e) + } + + reloadSmartspace() + } + + /** + * Disconnects the smartspace view from the smartspace service and cleans up any resources. + */ + private fun disconnect() { + if (hasActiveSessionListeners()) return + + execution.assertIsMainThread() + + if (session == null) { + return + } + + session?.let { + it.removeOnTargetsAvailableListener(sessionListener) + it.close() + } + + session = null + + plugin?.registerSmartspaceEventNotifier(null) + plugin?.onTargetsAvailable(emptyList()) + Log.d(TAG, "Ending smartspace session for dream") + } + + fun addListener(listener: SmartspaceTargetListener) { + execution.assertIsMainThread() + plugin?.registerListener(listener) + listeners.add(listener) + + connectSession() + } + + fun removeListener(listener: SmartspaceTargetListener) { + execution.assertIsMainThread() + plugin?.unregisterListener(listener) + listeners.remove(listener) + disconnect() + } + + private fun reloadSmartspace() { + session?.requestSmartspaceUpdate() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index bf689e30d2fd..2db3de173257 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -16,6 +16,7 @@ package com.android.systemui.flags; +import com.android.internal.annotations.Keep; import com.android.systemui.R; import java.lang.reflect.Field; @@ -154,8 +155,9 @@ public class Flags { new BooleanFlag(1000, true); // 1100 - windowing + @Keep public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS = - new SysPropBooleanFlag(1100, "persist.debug.shell_transit", false); + new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false); // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 7a278f786a67..af553c744311 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -624,7 +624,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene addIfShouldShowAction(tempActions, new LogoutAction()); } } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { - addIfShouldShowAction(tempActions, new EmergencyDialerAction()); + if (shouldDisplayEmergency()) { + addIfShouldShowAction(tempActions, new EmergencyDialerAction()); + } } else { Log.e(TAG, "Invalid global action key " + actionKey); } @@ -704,6 +706,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @VisibleForTesting + boolean shouldDisplayEmergency() { + // Emergency calling requires a telephony radio. + return mHasTelephony; + } + + @VisibleForTesting boolean shouldDisplayBugReport(UserInfo currentUser) { return mGlobalSettings.getInt(Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && (currentUser == null || currentUser.isPrimary()); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 831a606e95b2..510d15bd7b73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -54,6 +54,7 @@ import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.GhostedViewLaunchAnimatorController; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.monet.ColorScheme; @@ -104,10 +105,18 @@ public class MediaControlPanel { R.id.action4 }; + // Buttons to show in small player when using semantic actions + private static final List<Integer> SEMANTIC_ACTION_IDS = List.of( + R.id.actionPlayPause, + R.id.actionPrev, + R.id.actionNext + ); + private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; protected final Executor mBackgroundExecutor; private final ActivityStarter mActivityStarter; + private final BroadcastSender mBroadcastSender; private Context mContext; private MediaViewHolder mMediaViewHolder; @@ -142,14 +151,16 @@ public class MediaControlPanel { */ @Inject public MediaControlPanel(Context context, @Background Executor backgroundExecutor, - ActivityStarter activityStarter, MediaViewController mediaViewController, - SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, + ActivityStarter activityStarter, BroadcastSender broadcastSender, + MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel, + Lazy<MediaDataManager> lazyMediaDataManager, MediaOutputDialogFactory mediaOutputDialogFactory, MediaCarouselController mediaCarouselController, FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; + mBroadcastSender = broadcastSender; mSeekBarViewModel = seekBarViewModel; mMediaViewController = mediaViewController; mMediaDataManagerLazy = lazyMediaDataManager; @@ -501,11 +512,11 @@ public class MediaControlPanel { MediaButton semanticActions = data.getSemanticActions(); actionIcons = new ArrayList<MediaAction>(); - actionIcons.add(semanticActions.getStartCustom()); + actionIcons.add(semanticActions.getCustom0()); actionIcons.add(semanticActions.getPrevOrCustom()); actionIcons.add(semanticActions.getPlayOrPause()); actionIcons.add(semanticActions.getNextOrCustom()); - actionIcons.add(semanticActions.getEndCustom()); + actionIcons.add(semanticActions.getCustom1()); actionsWhenCollapsed = new ArrayList<Integer>(); actionsWhenCollapsed.add(1); @@ -562,6 +573,9 @@ public class MediaControlPanel { /** Bind elements specific to PlayerSessionViewHolder */ private void bindSessionPlayer(@NonNull MediaData data, String key) { + ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); + ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); + // Default colors int surfaceColor = mBackgroundColor; int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext, @@ -575,25 +589,6 @@ public class MediaControlPanel { int textTertiary = com.android.settingslib.Utils.getColorAttr(mContext, com.android.internal.R.attr.textColorTertiary).getDefaultColor(); - // App icon - use launcher icon - ImageView appIconView = mMediaViewHolder.getAppIcon(); - appIconView.clearColorFilter(); - try { - Drawable icon = mContext.getPackageManager().getApplicationIcon( - data.getPackageName()); - appIconView.setImageDrawable(icon); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e); - // Fall back to notification icon - if (data.getAppIcon() != null) { - appIconView.setImageIcon(data.getAppIcon()); - } else { - appIconView.setImageResource(R.drawable.ic_music_note); - } - int color = mContext.getColor(R.color.material_dynamic_secondary10); - appIconView.setColorFilter(color); - } - // Album art ColorScheme colorScheme = null; ImageView albumView = mMediaViewHolder.getAlbumView(); @@ -640,6 +635,25 @@ public class MediaControlPanel { ColorStateList.valueOf(surfaceColor)); mMediaViewHolder.getPlayer().setBackgroundTintList(bgColorList); + // App icon - use notification icon + ImageView appIconView = mMediaViewHolder.getAppIcon(); + appIconView.clearColorFilter(); + if (data.getAppIcon() != null && !data.getResumption()) { + appIconView.setImageIcon(data.getAppIcon()); + appIconView.setColorFilter(accentPrimary); + } else { + // Resume players use launcher icon + appIconView.setColorFilter(getGrayscaleFilter()); + try { + Drawable icon = mContext.getPackageManager().getApplicationIcon( + data.getPackageName()); + appIconView.setImageDrawable(icon); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e); + appIconView.setImageResource(R.drawable.ic_music_note); + } + } + // Metadata text mMediaViewHolder.getTitleText().setTextColor(textPrimary); mMediaViewHolder.getArtistText().setTextColor(textSecondary); @@ -660,26 +674,68 @@ public class MediaControlPanel { // Media action buttons MediaButton semanticActions = data.getSemanticActions(); + PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder; + ImageButton[] genericButtons = new ImageButton[]{ + sessionHolder.getAction0(), + sessionHolder.getAction1(), + sessionHolder.getAction2(), + sessionHolder.getAction3(), + sessionHolder.getAction4()}; + + ImageButton[] semanticButtons = new ImageButton[]{ + sessionHolder.getActionPlayPause(), + sessionHolder.getActionNext(), + sessionHolder.getActionPrev()}; + if (semanticActions != null) { - PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder; + // Hide all the generic buttons + for (ImageButton b: genericButtons) { + setVisibleAndAlpha(collapsedSet, b.getId(), false); + setVisibleAndAlpha(expandedSet, b.getId(), false); + } // Play/pause button has a background sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList); setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(), - ColorStateList.valueOf(textPrimaryInverse)); + ColorStateList.valueOf(textPrimaryInverse), collapsedSet, expandedSet, true); setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(), - textColorList); + textColorList, collapsedSet, expandedSet, true); setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(), - textColorList); - setSemanticButton(sessionHolder.getActionStart(), semanticActions.getStartCustom(), - textColorList); - setSemanticButton(sessionHolder.getActionEnd(), semanticActions.getEndCustom(), - textColorList); + textColorList, collapsedSet, expandedSet, true); + setSemanticButton(sessionHolder.getAction0(), semanticActions.getCustom0(), + textColorList, collapsedSet, expandedSet, false); + setSemanticButton(sessionHolder.getAction1(), semanticActions.getCustom1(), + textColorList, collapsedSet, expandedSet, false); } else { - Log.w(TAG, "Using semantic player, but did not get buttons"); + // Hide all the semantic buttons + for (int id : SEMANTIC_ACTION_IDS) { + setVisibleAndAlpha(collapsedSet, id, false); + setVisibleAndAlpha(expandedSet, id, false); + } + + // Set all the generic buttons + List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact(); + List<MediaAction> actions = data.getActions(); + int i = 0; + for (; i < actions.size(); i++) { + boolean showInCompact = actionsWhenCollapsed.contains(i); + setSemanticButton(genericButtons[i], actions.get(i), textColorList, collapsedSet, + expandedSet, showInCompact); + } + for (; i < 5; i++) { + // Hide any unused buttons + setSemanticButton(genericButtons[i], null, textColorList, collapsedSet, + expandedSet, false); + } } + // If disabled, set progress bar to INVISIBLE instead of GONE so layout weights still work + boolean seekbarEnabled = mSeekBarViewModel.getEnabled(); + expandedSet.setVisibility(R.id.media_progress_bar, + seekbarEnabled ? ConstraintSet.VISIBLE : ConstraintSet.INVISIBLE); + expandedSet.setAlpha(R.id.media_progress_bar, seekbarEnabled ? 1.0f : 0.0f); + // Long press buttons mMediaViewHolder.getLongPressText().setTextColor(textColorList); mMediaViewHolder.getSettingsText().setTextColor(textColorList); @@ -688,11 +744,11 @@ public class MediaControlPanel { mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList); mMediaViewHolder.getDismissText().setTextColor(textColorList); mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList); - } private void setSemanticButton(final ImageButton button, MediaAction mediaAction, - ColorStateList fgColor) { + ColorStateList fgColor, ConstraintSet collapsedSet, ConstraintSet expandedSet, + boolean showInCompact) { button.setImageTintList(fgColor); if (mediaAction != null) { button.setImageIcon(mediaAction.getIcon()); @@ -716,6 +772,9 @@ public class MediaControlPanel { button.setContentDescription(null); button.setEnabled(false); } + + setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact); + setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null); } @Nullable @@ -899,7 +958,7 @@ public class MediaControlPanel { // Dismiss the card Smartspace data through Smartspace trampoline activity. mContext.startActivity(dismissIntent); } else { - mContext.sendBroadcast(dismissIntent); + mBroadcastSender.sendBroadcast(dismissIntent); } }); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 500e82efdb0a..4cf6291fe35b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -149,11 +149,11 @@ data class MediaButton( /** * First custom action space */ - var startCustom: MediaAction? = null, + var custom0: MediaAction? = null, /** - * Last custom action space + * Second custom action space */ - var endCustom: MediaAction? = null + var custom1: MediaAction? = null ) /** State of a media action. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index ae5c1f2b19a9..de44a9c46963 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -21,6 +21,7 @@ import android.os.SystemProperties import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.CurrentUserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -56,6 +57,7 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties class MediaDataFilter @Inject constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, + private val broadcastSender: BroadcastSender, private val lockscreenUserManager: NotificationLockscreenUserManager, @Main private val executor: Executor, private val systemClock: SystemClock @@ -249,7 +251,7 @@ class MediaDataFilter @Inject constructor( // Dismiss the card Smartspace data through Smartspace trampoline activity. context.startActivity(dismissIntent) } else { - context.sendBroadcast(dismissIntent) + broadcastSender.sendBroadcast(dismissIntent) } smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index fab06c288ce1..9e14fe91f21d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -752,8 +752,8 @@ class MediaDataManager( null } - actions.startCustom = customActions[customIdx++] - actions.endCustom = customActions[customIdx++] + actions.custom0 = customActions[customIdx++] + actions.custom1 = customActions[customIdx++] } return actions } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index 77873e829be3..38604091c409 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -54,7 +54,7 @@ public class MediaProjectionPermissionActivity extends Activity private int mUid; private IMediaProjectionManager mService; - private SystemUIDialog mDialog; + private AlertDialog mDialog; @Override public void onCreate(Bundle icicle) { @@ -141,13 +141,18 @@ public class MediaProjectionPermissionActivity extends Activity dialogTitle = getString(R.string.media_projection_dialog_title, appName); } - mDialog = new SystemUIDialog(this); - mDialog.setTitle(dialogTitle); - mDialog.setIcon(R.drawable.ic_media_projection_permission); - mDialog.setMessage(dialogText); - mDialog.setPositiveButton(R.string.media_projection_action_text, this); - mDialog.setNeutralButton(android.R.string.cancel, this); - mDialog.setOnCancelListener(this); + mDialog = new AlertDialog.Builder(this, R.style.Theme_SystemUI_Dialog) + .setTitle(dialogTitle) + .setIcon(R.drawable.ic_media_projection_permission) + .setMessage(dialogText) + .setPositiveButton(R.string.media_projection_action_text, this) + .setNeutralButton(android.R.string.cancel, this) + .setOnCancelListener(this) + .create(); + + SystemUIDialog.registerDismissListener(mDialog); + SystemUIDialog.applyFlags(mDialog); + SystemUIDialog.setDialogSize(mDialog); mDialog.create(); mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); @@ -186,7 +191,7 @@ public class MediaProjectionPermissionActivity extends Activity private Intent getMediaProjectionIntent(int uid, String packageName) throws RemoteException { IMediaProjection projection = mService.createProjection(uid, packageName, - MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */); + MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */); Intent intent = new Intent(); intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder()); return intent; diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt index e57b247da055..5f606969153c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt @@ -60,12 +60,24 @@ abstract class MediaViewHolder constructor(itemView: View) { val settings = itemView.requireViewById<View>(R.id.settings) val settingsText = itemView.requireViewById<TextView>(R.id.settings_text) + // Action Buttons + val action0 = itemView.requireViewById<ImageButton>(R.id.action0) + val action1 = itemView.requireViewById<ImageButton>(R.id.action1) + val action2 = itemView.requireViewById<ImageButton>(R.id.action2) + val action3 = itemView.requireViewById<ImageButton>(R.id.action3) + val action4 = itemView.requireViewById<ImageButton>(R.id.action4) + init { (player.background as IlluminationDrawable).let { it.registerLightSource(seamless) it.registerLightSource(cancel) it.registerLightSource(dismiss) it.registerLightSource(settings) + it.registerLightSource(action0) + it.registerLightSource(action1) + it.registerLightSource(action2) + it.registerLightSource(action3) + it.registerLightSource(action4) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt index 87d2cffea257..6928ebb8bb32 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt @@ -31,16 +31,12 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause) val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext) val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev) - val actionStart = itemView.requireViewById<ImageButton>(R.id.actionStart) - val actionEnd = itemView.requireViewById<ImageButton>(R.id.actionEnd) init { (player.background as IlluminationDrawable).let { it.registerLightSource(actionPlayPause) it.registerLightSource(actionNext) it.registerLightSource(actionPrev) - it.registerLightSource(actionStart) - it.registerLightSource(actionEnd) } } @@ -49,8 +45,11 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol R.id.actionPlayPause -> actionPlayPause R.id.actionNext -> actionNext R.id.actionPrev -> actionPrev - R.id.actionStart -> actionStart - R.id.actionEnd -> actionEnd + R.id.action0 -> action0 + R.id.action1 -> action1 + R.id.action2 -> action2 + R.id.action3 -> action3 + R.id.action4 -> action4 else -> { throw IllegalArgumentException() } @@ -90,8 +89,11 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol R.id.actionPlayPause, R.id.actionNext, R.id.actionPrev, - R.id.actionStart, - R.id.actionEnd, + R.id.action0, + R.id.action1, + R.id.action2, + R.id.action3, + R.id.action4, R.id.icon ) val gutsIds = setOf( diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt index 20b2d4a452f5..dd3fa89dea66 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt @@ -33,23 +33,6 @@ class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(ite override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time) override val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time) - // Action Buttons - val action0 = itemView.requireViewById<ImageButton>(R.id.action0) - val action1 = itemView.requireViewById<ImageButton>(R.id.action1) - val action2 = itemView.requireViewById<ImageButton>(R.id.action2) - val action3 = itemView.requireViewById<ImageButton>(R.id.action3) - val action4 = itemView.requireViewById<ImageButton>(R.id.action4) - - init { - (player.background as IlluminationDrawable).let { - it.registerLightSource(action0) - it.registerLightSource(action1) - it.registerLightSource(action2) - it.registerLightSource(action3) - it.registerLightSource(action4) - } - } - override fun getAction(id: Int): ImageButton { return when (id) { R.id.action0 -> action0 diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index cf997055c692..57701ab618c9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -50,25 +50,46 @@ class SeekBarObserver( .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding) } + init { + val seekBarProgressWavelength = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength).toFloat() + val seekBarProgressAmplitude = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude).toFloat() + val seekBarProgressPhase = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase).toFloat() + val seekBarProgressStrokeWidth = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width).toFloat() + val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress + progressDrawable?.let { + it.waveLength = seekBarProgressWavelength + it.lineAmplitude = seekBarProgressAmplitude + it.phaseSpeed = seekBarProgressPhase + it.strokeWidth = seekBarProgressStrokeWidth + } + } + /** Updates seek bar views when the data model changes. */ @UiThread override fun onChanged(data: SeekBarViewModel.Progress) { + val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress if (!data.enabled) { if (holder.seekBar.maxHeight != seekBarDisabledHeight) { holder.seekBar.maxHeight = seekBarDisabledHeight setVerticalPadding(seekBarDisabledVerticalPadding) } - holder.seekBar.setEnabled(false) - holder.seekBar.getThumb().setAlpha(0) - holder.seekBar.setProgress(0) - holder.elapsedTimeView?.setText("") - holder.totalTimeView?.setText("") + holder.seekBar.isEnabled = false + progressDrawable?.animate = false + holder.seekBar.thumb.alpha = 0 + holder.seekBar.progress = 0 + holder.elapsedTimeView?.text = "" + holder.totalTimeView?.text = "" holder.seekBar.contentDescription = "" return } - holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0) - holder.seekBar.setEnabled(data.seekAvailable) + holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0 + holder.seekBar.isEnabled = data.seekAvailable + progressDrawable?.animate = data.playing if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) { holder.seekBar.maxHeight = seekBarEnabledMaxHeight diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index 9cf9c4815b67..49cd16175f0a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -31,6 +31,7 @@ import androidx.core.view.GestureDetectorCompat import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.util.concurrency.RepeatableExecutor import javax.inject.Inject @@ -73,7 +74,7 @@ private fun PlaybackState.computePosition(duration: Long): Long { class SeekBarViewModel @Inject constructor( @Background private val bgExecutor: RepeatableExecutor ) { - private var _data = Progress(false, false, null, 0) + private var _data = Progress(false, false, false, null, 0) set(value) { field = value _progress.postValue(value) @@ -131,6 +132,8 @@ class SeekBarViewModel @Inject constructor( lateinit var logSmartspaceClick: () -> Unit + fun getEnabled() = _data.enabled + /** * Event indicating that the user has started interacting with the seek bar. */ @@ -192,10 +195,12 @@ class SeekBarViewModel @Inject constructor( val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L val position = playbackState?.position?.toInt() val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0 + val playing = NotificationMediaManager + .isPlayingState(playbackState?.state ?: PlaybackState.STATE_NONE) val enabled = if (playbackState == null || playbackState?.getState() == PlaybackState.STATE_NONE || (duration <= 0)) false else true - _data = Progress(enabled, seekAvailable, position, duration) + _data = Progress(enabled, seekAvailable, playing, position, duration) checkIfPollingNeeded() } @@ -412,6 +417,7 @@ class SeekBarViewModel @Inject constructor( data class Progress( val enabled: Boolean, val seekAvailable: Boolean, + val playing: Boolean, val elapsedTime: Int?, val duration: Int ) diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt new file mode 100644 index 000000000000..f1058e28863a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt @@ -0,0 +1,184 @@ +package com.android.systemui.media + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.content.res.ColorStateList +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable +import android.os.SystemClock +import androidx.annotation.VisibleForTesting +import com.android.systemui.animation.Interpolators +import kotlin.math.abs +import kotlin.math.cos + +private const val TAG = "Squiggly" + +private const val TWO_PI = (Math.PI * 2f).toFloat() +@VisibleForTesting +internal const val DISABLED_ALPHA = 77 + +class SquigglyProgress : Drawable() { + + private val wavePaint = Paint() + private val linePaint = Paint() + private val path = Path() + private var heightFraction = 0f + private var heightAnimator: ValueAnimator? = null + private var phaseOffset = 0f + private var lastFrameTime = -1L + + // Horizontal length of the sine wave + var waveLength = 0f + // Height of each peak of the sine wave + var lineAmplitude = 0f + // Line speed in px per second + var phaseSpeed = 0f + // Progress stroke width, both for wave and solid line + var strokeWidth = 0f + set(value) { + if (field == value) { + return + } + field = value + wavePaint.strokeWidth = value + linePaint.strokeWidth = value + } + + init { + wavePaint.strokeCap = Paint.Cap.ROUND + linePaint.strokeCap = Paint.Cap.ROUND + linePaint.style = Paint.Style.STROKE + wavePaint.style = Paint.Style.STROKE + linePaint.alpha = DISABLED_ALPHA + } + + var animate: Boolean = false + set(value) { + if (field == value) { + return + } + field = value + if (field) { + lastFrameTime = SystemClock.uptimeMillis() + } + heightAnimator?.cancel() + heightAnimator = ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply { + if (animate) { + startDelay = 60 + duration = 800 + interpolator = Interpolators.EMPHASIZED_DECELERATE + } else { + duration = 550 + interpolator = Interpolators.STANDARD_DECELERATE + } + addUpdateListener { + heightFraction = it.animatedValue as Float + invalidateSelf() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + heightAnimator = null + } + }) + start() + } + } + + override fun draw(canvas: Canvas) { + if (animate) { + invalidateSelf() + val now = SystemClock.uptimeMillis() + phaseOffset -= (now - lastFrameTime) / 1000f * phaseSpeed + phaseOffset %= waveLength + lastFrameTime = now + } + + val totalProgressPx = (bounds.width() * (level / 10_000f)) + canvas.save() + canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) + // Clip drawing, so we stop at the thumb + canvas.clipRect( + 0f, + -lineAmplitude - strokeWidth, + totalProgressPx, + lineAmplitude + strokeWidth) + + // The squiggly line + val start = phaseOffset + var currentX = start + var waveSign = 1f + path.rewind() + path.moveTo(start, lineAmplitude * heightFraction) + while (currentX < totalProgressPx) { + val nextX = currentX + waveLength / 2f + val nextWaveSign = waveSign * -1 + path.cubicTo( + currentX + waveLength / 4f, lineAmplitude * waveSign * heightFraction, + nextX - waveLength / 4f, lineAmplitude * nextWaveSign * heightFraction, + nextX, lineAmplitude * nextWaveSign * heightFraction) + currentX = nextX + waveSign = nextWaveSign + } + wavePaint.style = Paint.Style.STROKE + canvas.drawPath(path, wavePaint) + canvas.restore() + + // Draw round line cap at the beginning of the wave + val startAmp = cos(abs(phaseOffset) / waveLength * TWO_PI) + val p = Paint() + p.color = Color.WHITE + canvas.drawPoint( + bounds.left.toFloat(), + bounds.centerY() + startAmp * lineAmplitude * heightFraction, + wavePaint) + + // Draw continuous line, to the right of the thumb + canvas.drawLine( + bounds.left.toFloat() + totalProgressPx, + bounds.centerY().toFloat(), + bounds.width().toFloat(), + bounds.centerY().toFloat(), + linePaint) + } + + override fun getOpacity(): Int { + return PixelFormat.TRANSLUCENT + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + wavePaint.colorFilter = colorFilter + linePaint.colorFilter = colorFilter + } + + override fun setAlpha(alpha: Int) { + wavePaint.alpha = alpha + linePaint.alpha = (DISABLED_ALPHA * (alpha / 255f)).toInt() + } + + override fun getAlpha(): Int { + return wavePaint.alpha + } + + override fun setTint(tintColor: Int) { + wavePaint.color = tintColor + linePaint.color = tintColor + } + + override fun onLevelChange(level: Int): Boolean { + return animate + } + + override fun setTintList(tint: ColorStateList?) { + if (tint == null) { + return + } + wavePaint.color = tint.defaultColor + linePaint.color = tint.defaultColor + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 355c69f9a48d..a8141c0e2fec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -21,7 +21,6 @@ import static android.view.WindowInsets.Type.statusBars; import android.app.WallpaperColors; import android.content.Context; -import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -55,6 +54,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.statusbar.phone.SystemUIDialog; /** @@ -71,6 +71,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements final Context mContext; final MediaOutputController mMediaOutputController; + final BroadcastSender mBroadcastSender; @VisibleForTesting View mDialogView; @@ -98,11 +99,13 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } }; - public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) { + public MediaOutputBaseDialog(Context context, BroadcastSender broadcastSender, + MediaOutputController mediaOutputController) { super(context, R.style.Theme_SystemUI_Dialog_Media); // Save the context that is wrapped with our theme. mContext = getContext(); + mBroadcastSender = broadcastSender; mMediaOutputController = mediaOutputController; mLayoutManager = new LinearLayoutManager(mContext); mListMaxHeight = context.getResources().getDimensionPixelSize( @@ -152,7 +155,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements dismiss(); }); mAppButton.setOnClickListener(v -> { - mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + mBroadcastSender.closeSystemDialogs(); if (mMediaOutputController.getAppLaunchIntent() != null) { mContext.startActivity(mMediaOutputController.getAppLaunchIntent()); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 0c202e09b62e..0b6c68d17a4c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -53,7 +53,6 @@ import androidx.core.graphics.drawable.IconCompat; import androidx.mediarouter.media.MediaRouter; import androidx.mediarouter.media.MediaRouterParams; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -70,7 +69,6 @@ import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; -import com.android.systemui.statusbar.phone.ShadeController; import java.util.ArrayList; import java.util.Collection; @@ -95,12 +93,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private final String mPackageName; private final Context mContext; private final MediaSessionManager mMediaSessionManager; - private final LocalBluetoothManager mLocalBluetoothManager; - private final ShadeController mShadeController; private final ActivityStarter mActivityStarter; private final DialogLaunchAnimator mDialogLaunchAnimator; private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); - private final boolean mAboveStatusbar; private final CommonNotifCollection mNotifCollection; @VisibleForTesting final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); @@ -114,7 +109,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, LocalMediaManager mLocalMediaManager; private MediaOutputMetricLogger mMetricLogger; - private UiEventLogger mUiEventLogger; private int mColorActiveItem; private int mColorInactiveItem; @@ -124,23 +118,19 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @Inject public MediaOutputController(@NonNull Context context, String packageName, - boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager - lbm, ShadeController shadeController, ActivityStarter starter, - CommonNotifCollection notifCollection, UiEventLogger uiEventLogger, + MediaSessionManager mediaSessionManager, LocalBluetoothManager + lbm, ActivityStarter starter, + CommonNotifCollection notifCollection, DialogLaunchAnimator dialogLaunchAnimator, Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional) { mContext = context; mPackageName = packageName; mMediaSessionManager = mediaSessionManager; - mLocalBluetoothManager = lbm; - mShadeController = shadeController; mActivityStarter = starter; - mAboveStatusbar = aboveStatusbar; mNotifCollection = notifCollection; InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); - mUiEventLogger = uiEventLogger; mDialogLaunchAnimator = dialogLaunchAnimator; mNearbyMediaDevicesManager = nearbyMediaDevicesManagerOptional.orElse(null); mColorActiveItem = Utils.getColorStateListDefaultColor(mContext, @@ -630,18 +620,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mActivityStarter.startActivity(launchIntent, true, controller); } - void launchMediaOutputGroupDialog(View mediaOutputDialog) { - // We show the output group dialog from the output dialog. - MediaOutputController controller = new MediaOutputController(mContext, mPackageName, - mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController, - mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager)); - - MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar, - controller); - mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); - } - boolean isActiveRemoteDevice(@NonNull MediaDevice device) { final List<String> features = device.getFeatures(); return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK) diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index 7696a1f63c01..7834ec0fa17f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -28,6 +28,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; /** @@ -37,9 +38,9 @@ import com.android.systemui.dagger.SysUISingleton; public class MediaOutputDialog extends MediaOutputBaseDialog { final UiEventLogger mUiEventLogger; - MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController - mediaOutputController, UiEventLogger uiEventLogger) { - super(context, mediaOutputController); + MediaOutputDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, + MediaOutputController mediaOutputController, UiEventLogger uiEventLogger) { + super(context, broadcastSender, mediaOutputController); mUiEventLogger = uiEventLogger; mAdapter = new MediaOutputAdapter(mMediaOutputController, this); if (!aboveStatusbar) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index a7e54801bf47..0d7d60ac5923 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -22,10 +22,10 @@ import android.view.View import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.nearby.NearbyMediaDevicesManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection -import com.android.systemui.statusbar.phone.ShadeController import java.util.Optional import javax.inject.Inject @@ -36,8 +36,8 @@ class MediaOutputDialogFactory @Inject constructor( private val context: Context, private val mediaSessionManager: MediaSessionManager, private val lbm: LocalBluetoothManager?, - private val shadeController: ShadeController, private val starter: ActivityStarter, + private val broadcastSender: BroadcastSender, private val notifCollection: CommonNotifCollection, private val uiEventLogger: UiEventLogger, private val dialogLaunchAnimator: DialogLaunchAnimator, @@ -52,10 +52,11 @@ class MediaOutputDialogFactory @Inject constructor( // Dismiss the previous dialog, if any. mediaOutputDialog?.dismiss() - val controller = MediaOutputController(context, packageName, aboveStatusBar, - mediaSessionManager, lbm, shadeController, starter, notifCollection, - uiEventLogger, dialogLaunchAnimator, nearbyMediaDevicesManagerOptional) - val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger) + val controller = MediaOutputController(context, packageName, + mediaSessionManager, lbm, starter, notifCollection, + dialogLaunchAnimator, nearbyMediaDevicesManagerOptional) + val dialog = + MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger) mediaOutputDialog = dialog // Show the dialog. diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java index f1c66016a49a..bb3f969c86df 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java @@ -25,6 +25,7 @@ import android.view.WindowManager; import androidx.core.graphics.drawable.IconCompat; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastSender; /** * Dialog for media output group. @@ -32,9 +33,9 @@ import com.android.systemui.R; // TODO(b/203073091): Remove this class once group logic been implemented. public class MediaOutputGroupDialog extends MediaOutputBaseDialog { - MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController - mediaOutputController) { - super(context, mediaOutputController); + MediaOutputGroupDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, + MediaOutputController mediaOutputController) { + super(context, broadcastSender, mediaOutputController); mMediaOutputController.resetGroupMediaDevices(); mAdapter = new MediaOutputGroupAdapter(mMediaOutputController); if (!aboveStatusbar) { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 81934a6b6a73..71cacac7f4df 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -22,6 +22,8 @@ import android.content.Context import android.content.pm.PackageManager import android.graphics.PixelFormat import android.graphics.drawable.Drawable +import android.os.PowerManager +import android.os.SystemClock import android.util.Log import android.view.Gravity import android.view.LayoutInflater @@ -53,6 +55,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( private val viewUtil: ViewUtil, @Main private val mainExecutor: DelayableExecutor, private val tapGestureDetector: TapGestureDetector, + private val powerManager: PowerManager, @LayoutRes private val chipLayoutRes: Int ) { /** The window layout parameters we'll use when attaching the view to a window. */ @@ -95,6 +98,12 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( if (oldChipView == null) { tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped) windowManager.addView(chipView, windowLayoutParams) + // Wake the screen so the user will see the chip + powerManager.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "com.android.systemui:media_tap_to_transfer_activated" + ) } // Cancel and re-set the chip timeout each time we get a new state. diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 3b94a1d9597e..44965d705802 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.media.MediaRoute2Info import android.os.Handler +import android.os.PowerManager import android.util.Log import android.view.ViewGroup import android.view.WindowManager @@ -52,6 +53,7 @@ class MediaTttChipControllerReceiver @Inject constructor( viewUtil: ViewUtil, mainExecutor: DelayableExecutor, tapGestureDetector: TapGestureDetector, + powerManager: PowerManager, @Main private val mainHandler: Handler, private val uiEventLogger: MediaTttReceiverUiEventLogger, ) : MediaTttChipControllerCommon<ChipReceiverInfo>( @@ -61,6 +63,7 @@ class MediaTttChipControllerReceiver @Inject constructor( viewUtil, mainExecutor, tapGestureDetector, + powerManager, R.layout.media_ttt_chip_receiver ) { private val commandQueueCallbacks = object : CommandQueue.Callbacks { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 3e4cf9921bad..9f5ec7e1a330 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.taptotransfer.sender import android.app.StatusBarManager import android.content.Context import android.media.MediaRoute2Info +import android.os.PowerManager import android.util.Log import android.view.View import android.view.ViewGroup @@ -51,6 +52,7 @@ class MediaTttChipControllerSender @Inject constructor( viewUtil: ViewUtil, @Main mainExecutor: DelayableExecutor, tapGestureDetector: TapGestureDetector, + powerManager: PowerManager, private val uiEventLogger: MediaTttSenderUiEventLogger ) : MediaTttChipControllerCommon<ChipSenderInfo>( context, @@ -59,6 +61,7 @@ class MediaTttChipControllerSender @Inject constructor( viewUtil, mainExecutor, tapGestureDetector, + powerManager, R.layout.media_ttt_chip ) { private var currentlyDisplayedChipState: ChipStateSender? = null diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index b4e20fd7f32b..2ac34b22be5b 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -60,6 +60,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.utils.PowerUtil; import com.android.systemui.R; import com.android.systemui.SystemUIApplication; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -160,17 +161,20 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { @VisibleForTesting SystemUIDialog mUsbHighTempDialog; private BatteryStateSnapshot mCurrentBatterySnapshot; private ActivityStarter mActivityStarter; + private final BroadcastSender mBroadcastSender; /** */ @Inject - public PowerNotificationWarnings(Context context, ActivityStarter activityStarter) { + public PowerNotificationWarnings(Context context, ActivityStarter activityStarter, + BroadcastSender broadcastSender) { mContext = context; mNoMan = mContext.getSystemService(NotificationManager.class); mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mKeyguard = mContext.getSystemService(KeyguardManager.class); mReceiver.init(); mActivityStarter = activityStarter; + mBroadcastSender = broadcastSender; mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog); } @@ -258,7 +262,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { protected void showWarningNotification() { if (showSevereLowBatteryDialog()) { - mContext.sendBroadcast(new Intent(ACTION_ENABLE_SEVERE_BATTERY_DIALOG) + mBroadcastSender.sendBroadcast(new Intent(ACTION_ENABLE_SEVERE_BATTERY_DIALOG) .setPackage(mContext.getPackageName()) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND)); @@ -716,9 +720,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mSaverConfirmation.dismiss(); } // Also close the notification shade, if it's open. - mContext.sendBroadcast( + mBroadcastSender.sendBroadcast( new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) - .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)); + .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)); final Uri uri = Uri.parse(getURL()); Context context = widget.getContext(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 5d9361d201c1..e0d158cd7db6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -18,7 +18,10 @@ package com.android.systemui.qs import android.app.IActivityManager import android.app.IForegroundServiceObserver +import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.os.IBinder @@ -42,6 +45,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -67,6 +71,7 @@ class FgsManagerController @Inject constructor( private val packageManager: PackageManager, private val deviceConfigProxy: DeviceConfigProxy, private val dialogLaunchAnimator: DialogLaunchAnimator, + private val broadcastDispatcher: BroadcastDispatcher, private val dumpManager: DumpManager ) : IForegroundServiceObserver.Stub(), Dumpable { @@ -125,6 +130,18 @@ class FgsManagerController @Inject constructor( dumpManager.registerDumpable(this) + broadcastDispatcher.registerReceiver( + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER) { + showDialog(null) + } + } + }, + IntentFilter(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER), + executor = mainExecutor, + flags = Context.RECEIVER_NOT_EXPORTED) + initialized = true } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index e1d20706c625..8bad2de189c5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -47,9 +47,6 @@ import java.util.concurrent.atomic.AtomicReference; public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> { private static final String TAG = "InternetAdapter"; - private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG"; - private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; - private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"; private final InternetDialogController mInternetDialogController; @Nullable @@ -169,11 +166,10 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern } mWifiListLayout.setOnClickListener(v -> { if (wifiEntry.shouldEditBeforeConnect()) { - final Intent intent = new Intent(ACTION_WIFI_DIALOG); + final Intent intent = WifiUtils.getWifiDialogIntent(wifiEntry.getKey(), + true /* connectForCaller */); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey()); - intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false); mContext.startActivity(intent); } mInternetDialogController.connect(wifiEntry); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index b3bc3be852fb..b322cbf6c60e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -112,7 +112,6 @@ public class InternetDialogController implements AccessPointController.AccessPoi "android.settings.NETWORK_PROVIDER_SETTINGS"; private static final String ACTION_WIFI_SCANNING_SETTINGS = "android.settings.WIFI_SCANNING_SETTINGS"; - private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); public static final int NO_CELL_DATA_TYPE_ICON = 0; private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off; @@ -853,8 +852,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi } if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) { - final Intent intent = new Intent("com.android.settings.WIFI_DIALOG") - .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey()); + final Intent intent = WifiUtils.getWifiDialogIntent(mWifiEntry.getKey(), + true /* connectForCaller */); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mActivityStarter.startActivity(intent, false /* dismissShade */); } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java new file mode 100644 index 000000000000..0b987677eac9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java @@ -0,0 +1,354 @@ +/* + * 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.systemui.screenshot; + +import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; +import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.MathUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.android.systemui.R; + +/** + * ConstraintLayout that is draggable when touched in a specific region + */ +public class DraggableConstraintLayout extends ConstraintLayout + implements ViewTreeObserver.OnComputeInternalInsetsListener { + + private final SwipeDismissHandler mSwipeDismissHandler; + private final GestureDetector mSwipeDetector; + private View mActionsContainer; + private SwipeDismissCallbacks mCallbacks; + + /** + * Stores the callbacks when the view is interacted with or dismissed. + */ + public interface SwipeDismissCallbacks { + /** + * Run when the view is interacted with (touched) + */ + default void onInteraction() { + + } + + /** + * Run when the view is dismissed (the distance threshold is met), pre-dismissal animation + */ + default void onSwipeDismissInitiated(Animator animator) { + + } + + /** + * Run when the view is dismissed (the distance threshold is met), post-dismissal animation + */ + default void onDismissComplete() { + + } + } + + public DraggableConstraintLayout(Context context) { + this(context, null); + } + + public DraggableConstraintLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + mSwipeDismissHandler = new SwipeDismissHandler(mContext, this); + setOnTouchListener(mSwipeDismissHandler); + + mSwipeDetector = new GestureDetector(mContext, + new GestureDetector.SimpleOnGestureListener() { + final Rect mActionsRect = new Rect(); + + @Override + public boolean onScroll( + MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { + mActionsContainer.getBoundsOnScreen(mActionsRect); + // return true if we aren't in the actions bar, or if we are but it isn't + // scrollable in the direction of movement + return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY()) + || !mActionsContainer.canScrollHorizontally((int) distanceX); + } + }); + mSwipeDetector.setIsLongpressEnabled(false); + } + + public void setCallbacks(SwipeDismissCallbacks callbacks) { + mCallbacks = callbacks; + } + + @Override // View + protected void onFinishInflate() { + mActionsContainer = findViewById(R.id.actions_container_background); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + mSwipeDismissHandler.onTouch(this, ev); + } + return mSwipeDetector.onTouchEvent(ev); + } + + public int getVisibleRight() { + return mActionsContainer.getRight(); + } + + /** + * Cancel current dismissal animation, if any + */ + public void cancelDismissal() { + mSwipeDismissHandler.cancel(); + } + + /** + * Return whether the view is currently dismissing + */ + public boolean isDismissing() { + return mSwipeDismissHandler.isDismissing(); + } + + /** + * Dismiss the view, with animation controlled by SwipeDismissHandler + */ + public void dismiss() { + mSwipeDismissHandler.dismiss(); + } + + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + // Only child views are touchable. + Region r = new Region(); + Rect rect = new Rect(); + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).getGlobalVisibleRect(rect); + r.op(rect, Region.Op.UNION); + } + inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + inoutInfo.touchableRegion.set(r); + } + + /** + * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not + * met + */ + private class SwipeDismissHandler implements OnTouchListener { + private static final String TAG = "SwipeDismissHandler"; + + // distance needed to register a dismissal + private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20; + + private final DraggableConstraintLayout mView; + private final GestureDetector mGestureDetector; + private final DisplayMetrics mDisplayMetrics; + private ValueAnimator mDismissAnimation; + + private float mStartX; + // Keeps track of the most recent direction (between the last two move events). + // -1 for left; +1 for right. + private int mDirectionX; + private float mPreviousX; + + SwipeDismissHandler(Context context, DraggableConstraintLayout view) { + mView = view; + GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener(); + mGestureDetector = new GestureDetector(context, gestureListener); + mDisplayMetrics = new DisplayMetrics(); + context.getDisplay().getRealMetrics(mDisplayMetrics); + mCallbacks = new SwipeDismissCallbacks() { + }; // default to unimplemented callbacks + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + boolean gestureResult = mGestureDetector.onTouchEvent(event); + mCallbacks.onInteraction(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mStartX = event.getRawX(); + mPreviousX = mStartX; + return true; + } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { + if (mDismissAnimation != null && mDismissAnimation.isRunning()) { + return true; + } + if (isPastDismissThreshold()) { + dismiss(); + } else { + // if we've moved, but not past the threshold, start the return animation + if (DEBUG_DISMISS) { + Log.d(TAG, "swipe gesture abandoned"); + } + createSwipeReturnAnimation().start(); + } + return true; + } + return gestureResult; + } + + class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onScroll( + MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { + mView.setTranslationX(ev2.getRawX() - mStartX); + mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1; + mPreviousX = ev2.getRawX(); + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (mView.getTranslationX() * velocityX > 0 + && (mDismissAnimation == null || !mDismissAnimation.isRunning())) { + ValueAnimator dismissAnimator = + createSwipeDismissAnimation(velocityX / (float) 1000); + mCallbacks.onSwipeDismissInitiated(dismissAnimator); + dismiss(dismissAnimator); + return true; + } + return false; + } + } + + private boolean isPastDismissThreshold() { + float translationX = mView.getTranslationX(); + // Determines whether the absolute translation from the start is in the same direction + // as the current movement. For example, if the user moves most of the way to the right, + // but then starts dragging back left, we do not dismiss even though the absolute + // distance is greater than the threshold. + if (translationX * mDirectionX > 0) { + return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics, + DISMISS_DISTANCE_THRESHOLD_DP); + } + return false; + } + + boolean isDismissing() { + return (mDismissAnimation != null && mDismissAnimation.isRunning()); + } + + void cancel() { + if (isDismissing()) { + if (DEBUG_ANIM) { + Log.d(TAG, "cancelling dismiss animation"); + } + mDismissAnimation.cancel(); + } + } + + void dismiss() { + ValueAnimator anim = createSwipeDismissAnimation(3); + mCallbacks.onSwipeDismissInitiated(anim); + dismiss(anim); + } + + private void dismiss(ValueAnimator animator) { + mDismissAnimation = animator; + mDismissAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (!mCancelled) { + mCallbacks.onDismissComplete(); + } + } + }); + mDismissAnimation.start(); + } + + private ValueAnimator createSwipeDismissAnimation(float velocity) { + // velocity is measured in pixels per millisecond + velocity = Math.min(3, Math.max(1, velocity)); + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + float startX = mView.getTranslationX(); + // make sure the UI gets all the way off the screen in the direction of movement + // (the actions container background is guaranteed to be both the leftmost and + // rightmost UI element in LTR and RTL) + float finalX; + int layoutDir = + mView.getContext().getResources().getConfiguration().getLayoutDirection(); + if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) { + finalX = mDisplayMetrics.widthPixels; + } else { + finalX = -1 * mActionsContainer.getRight(); + } + float distance = Math.abs(finalX - startX); + + anim.addUpdateListener(animation -> { + float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); + mView.setTranslationX(translation); + mView.setAlpha(1 - animation.getAnimatedFraction()); + }); + anim.setDuration((long) (distance / Math.abs(velocity))); + return anim; + } + + private ValueAnimator createSwipeReturnAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + float startX = mView.getTranslationX(); + float finalX = 0; + + anim.addUpdateListener(animation -> { + float translation = MathUtils.lerp( + startX, finalX, animation.getAnimatedFraction()); + mView.setTranslationX(translation); + }); + + return anim; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 50765f227554..009d4b9b48e6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -88,10 +88,12 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.ClipboardOverlayController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; +import com.android.systemui.util.Assert; import com.google.common.util.concurrent.ListenableFuture; @@ -247,6 +249,7 @@ public class ScreenshotController { private final ImageExporter mImageExporter; private final Executor mMainExecutor; private final ExecutorService mBgExecutor; + private final BroadcastSender mBroadcastSender; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; @@ -271,7 +274,6 @@ public class ScreenshotController { private String mPackageName = ""; private BroadcastReceiver mCopyBroadcastReceiver; - /** Tracks config changes that require re-creating UI */ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_ORIENTATION @@ -293,7 +295,8 @@ public class ScreenshotController { ScrollCaptureController scrollCaptureController, LongScreenshotData longScreenshotHolder, ActivityManager activityManager, - TimeoutHandler timeoutHandler) { + TimeoutHandler timeoutHandler, + BroadcastSender broadcastSender) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mScrollCaptureClient = scrollCaptureClient; @@ -304,6 +307,7 @@ public class ScreenshotController { mLongScreenshotHolder = longScreenshotHolder; mIsLowRamDevice = activityManager.isLowRamDevice(); mBgExecutor = Executors.newSingleThreadExecutor(); + mBroadcastSender = broadcastSender; mScreenshotHandler = timeoutHandler; mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); @@ -355,8 +359,10 @@ public class ScreenshotController { ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); } + @MainThread void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { + Assert.isMainThread(); mCurrentRequestCallback = requestCallback; DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); @@ -365,11 +371,12 @@ public class ScreenshotController { new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); } + @MainThread void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { // TODO: use task Id, userId, topComponent for smart handler - + Assert.isMainThread(); if (screenshot == null) { Log.e(TAG, "Got null bitmap from screenshot message"); mNotificationsController.notifyScreenshotError( @@ -392,8 +399,10 @@ public class ScreenshotController { /** * Displays a screenshot selector */ + @MainThread void takeScreenshotPartial(ComponentName topComponent, final Consumer<Uri> finisher, RequestCallback requestCallback) { + Assert.isMainThread(); mScreenshotView.reset(); mCurrentRequestCallback = requestCallback; @@ -517,7 +526,7 @@ public class ScreenshotController { saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true); - mContext.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), + mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), ClipboardOverlayController.SELF_PERMISSION); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 6d729b92e7b8..6af6e36a75f7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -141,7 +141,7 @@ public class ScreenshotView extends FrameLayout implements private ScreenshotSelectorView mScreenshotSelectorView; private ImageView mScrollingScrim; - private View mScreenshotStatic; + private DraggableConstraintLayout mScreenshotStatic; private ImageView mScreenshotPreview; private View mScreenshotPreviewBorder; private ImageView mScrollablePreview; @@ -159,7 +159,6 @@ public class ScreenshotView extends FrameLayout implements private UiEventLogger mUiEventLogger; private ScreenshotViewCallback mCallbacks; private boolean mPendingSharedTransition; - private SwipeDismissHandler mSwipeDismissHandler; private InputMonitorCompat mInputMonitor; private InputChannelCompat.InputEventReceiver mInputEventReceiver; private boolean mShowScrollablePreview; @@ -332,19 +331,6 @@ public class ScreenshotView extends FrameLayout implements } } - @Override // ViewGroup - public boolean onInterceptTouchEvent(MotionEvent ev) { - // scrolling scrim should not be swipeable; return early if we're on the scrim - if (!getSwipeRegion().contains((int) ev.getRawX(), (int) ev.getRawY())) { - return false; - } - // always pass through the down event so the swipe handler knows the initial state - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { - mSwipeDismissHandler.onTouch(this, ev); - } - return mSwipeDetector.onTouchEvent(ev); - } - @Override // View protected void onFinishInflate() { mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim)); @@ -356,8 +342,8 @@ public class ScreenshotView extends FrameLayout implements mScreenshotPreview.setClipToOutline(true); mActionsContainerBackground = requireNonNull(findViewById( - R.id.screenshot_actions_container_background)); - mActionsContainer = requireNonNull(findViewById(R.id.screenshot_actions_container)); + R.id.actions_container_background)); + mActionsContainer = requireNonNull(findViewById(R.id.actions_container)); mActionsView = requireNonNull(findViewById(R.id.screenshot_actions)); mBackgroundProtection = requireNonNull( findViewById(R.id.screenshot_actions_background)); @@ -395,35 +381,34 @@ public class ScreenshotView extends FrameLayout implements setFocusableInTouchMode(true); requestFocus(); - mSwipeDismissHandler = new SwipeDismissHandler(mContext, mScreenshotStatic, - new SwipeDismissHandler.SwipeDismissCallbacks() { - @Override - public void onInteraction() { - mCallbacks.onUserInteraction(); - } - - @Override - public void onSwipeDismissInitiated(Animator anim) { - if (DEBUG_DISMISS) { - Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture"); - } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, - mPackageName); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); - mBackgroundProtection.animate() - .alpha(0).setDuration(anim.getDuration()).start(); - } - }); - } + mScreenshotStatic.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() { + @Override + public void onInteraction() { + mCallbacks.onUserInteraction(); + } + @Override + public void onSwipeDismissInitiated(Animator animator) { + if (DEBUG_DISMISS) { + Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture"); + } + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, + mPackageName); + animator.addListener(new AnimatorListenerAdapter() { @Override - public void onDismissComplete() { - mCallbacks.onDismiss(); + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mBackgroundProtection.animate() + .alpha(0).setDuration(animation.getDuration()).start(); } }); + } + + @Override + public void onDismissComplete() { + mCallbacks.onDismiss(); + } + }); } View getScreenshotPreview() { @@ -648,8 +633,6 @@ public class ScreenshotView extends FrameLayout implements requestLayout(); createScreenshotActionsShadeAnimation().start(); - - setOnTouchListener(mSwipeDismissHandler); } }); @@ -958,7 +941,7 @@ public class ScreenshotView extends FrameLayout implements } boolean isDismissing() { - return mSwipeDismissHandler.isDismissing(); + return mScreenshotStatic.isDismissing(); } boolean isPendingSharedTransition() { @@ -966,15 +949,14 @@ public class ScreenshotView extends FrameLayout implements } void animateDismissal() { - mSwipeDismissHandler.dismiss(); + mScreenshotStatic.dismiss(); } void reset() { if (DEBUG_UI) { Log.d(TAG, "reset screenshot view"); } - - mSwipeDismissHandler.cancel(); + mScreenshotStatic.cancelDismissal(); if (DEBUG_WINDOW) { Log.d(TAG, "removing OnComputeInternalInsetsListener"); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java deleted file mode 100644 index 24b1249c3f98..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java +++ /dev/null @@ -1,237 +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.systemui.screenshot; - -import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; -import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.content.Context; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.MathUtils; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; - -/** - * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not met - */ -public class SwipeDismissHandler implements View.OnTouchListener { - private static final String TAG = "SwipeDismissHandler"; - - // distance needed to register a dismissal - private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20; - - /** - * Stores the callbacks when the view is interacted with or dismissed. - */ - public interface SwipeDismissCallbacks { - /** - * Run when the view is interacted with (touched) - */ - void onInteraction(); - - /** - * Run when the view is dismissed (the distance threshold is met), pre-dismissal animation - */ - void onSwipeDismissInitiated(Animator animator); - - /** - * Run when the view is dismissed (the distance threshold is met), post-dismissal animation - */ - void onDismissComplete(); - } - - private final View mView; - private final SwipeDismissCallbacks mCallbacks; - private final GestureDetector mGestureDetector; - private DisplayMetrics mDisplayMetrics; - private ValueAnimator mDismissAnimation; - - - private float mStartX; - // Keeps track of the most recent direction (between the last two move events). - // -1 for left; +1 for right. - private int mDirectionX; - private float mPreviousX; - - public SwipeDismissHandler(Context context, View view, SwipeDismissCallbacks callbacks) { - mView = view; - mCallbacks = callbacks; - GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener(); - mGestureDetector = new GestureDetector(context, gestureListener); - mDisplayMetrics = new DisplayMetrics(); - context.getDisplay().getRealMetrics(mDisplayMetrics); - } - - @Override - public boolean onTouch(View view, MotionEvent event) { - boolean gestureResult = mGestureDetector.onTouchEvent(event); - mCallbacks.onInteraction(); - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mStartX = event.getRawX(); - mPreviousX = mStartX; - return true; - } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { - if (mDismissAnimation != null && mDismissAnimation.isRunning()) { - return true; - } - if (isPastDismissThreshold()) { - ValueAnimator dismissAnimator = createSwipeDismissAnimation(1); - mCallbacks.onSwipeDismissInitiated(dismissAnimator); - dismiss(dismissAnimator); - } else { - // if we've moved, but not past the threshold, start the return animation - if (DEBUG_DISMISS) { - Log.d(TAG, "swipe gesture abandoned"); - } - createSwipeReturnAnimation().start(); - } - return true; - } - return gestureResult; - } - - class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onScroll( - MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { - mView.setTranslationX(ev2.getRawX() - mStartX); - mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1; - mPreviousX = ev2.getRawX(); - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - if (mView.getTranslationX() * velocityX > 0 - && (mDismissAnimation == null || !mDismissAnimation.isRunning())) { - ValueAnimator dismissAnimator = - createSwipeDismissAnimation(velocityX / (float) 1000); - mCallbacks.onSwipeDismissInitiated(dismissAnimator); - dismiss(dismissAnimator); - return true; - } - return false; - } - } - - private boolean isPastDismissThreshold() { - float translationX = mView.getTranslationX(); - // Determines whether the absolute translation from the start is in the same direction - // as the current movement. For example, if the user moves most of the way to the right, - // but then starts dragging back left, we do not dismiss even though the absolute - // distance is greater than the threshold. - if (translationX * mDirectionX > 0) { - return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics, - DISMISS_DISTANCE_THRESHOLD_DP); - } - return false; - } - - /** - * Return whether the view is currently being dismissed - */ - public boolean isDismissing() { - return (mDismissAnimation != null && mDismissAnimation.isRunning()); - } - - /** - * Cancel the currently-running dismissal animation, if any. - */ - public void cancel() { - if (isDismissing()) { - if (DEBUG_ANIM) { - Log.d(TAG, "cancelling dismiss animation"); - } - mDismissAnimation.cancel(); - } - } - - /** - * Start dismissal animation (will run onDismiss callback when animation complete) - */ - public void dismiss() { - dismiss(createSwipeDismissAnimation(1)); - } - - private void dismiss(ValueAnimator animator) { - mDismissAnimation = animator; - mDismissAnimation.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (!mCancelled) { - mCallbacks.onDismissComplete(); - } - } - }); - mDismissAnimation.start(); - } - - private ValueAnimator createSwipeDismissAnimation(float velocity) { - // velocity is measured in pixels per millisecond - velocity = Math.min(3, Math.max(1, velocity)); - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - float startX = mView.getTranslationX(); - // make sure the UI gets all the way off the screen in the direction of movement - // (the actions container background is guaranteed to be both the leftmost and - // rightmost UI element in LTR and RTL) - float finalX; - int layoutDir = mView.getContext().getResources().getConfiguration().getLayoutDirection(); - if (startX > 0 || (startX == 0 && layoutDir == View.LAYOUT_DIRECTION_RTL)) { - finalX = mDisplayMetrics.widthPixels; - } else { - finalX = -1 * mView.getRight(); - } - float distance = Math.abs(finalX - startX); - - anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); - mView.setTranslationX(translation); - mView.setAlpha(1 - animation.getAnimatedFraction()); - }); - anim.setDuration((long) (distance / Math.abs(velocity))); - return anim; - } - - private ValueAnimator createSwipeReturnAnimation() { - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - float startX = mView.getTranslationX(); - float finalX = 0; - - anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp( - startX, finalX, animation.getAnimatedFraction()); - mView.setTranslationX(translation); - }); - - return anim; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 98e6bd141b65..924351df3117 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -51,7 +51,6 @@ import androidx.annotation.NonNull; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.R; -import com.android.systemui.shared.recents.utilities.BitmapUtil; import java.util.function.Consumer; @@ -208,7 +207,7 @@ public class TakeScreenshotService extends Service { if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE"); } - Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap( + Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap( screenshotRequest.getBitmapBundle()); Rect screenBounds = screenshotRequest.getBoundsInScreen(); Insets insets = screenshotRequest.getInsets(); diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt b/packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt new file mode 100644 index 000000000000..aa2bcecbd224 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace + +/** + * A {@link SmartspacePrecondition} captures the conditions that must be met for Smartspace to be + * used in a particular setting. + */ +interface SmartspacePrecondition { + /** + * A callback for receiving updates when conditions have changed. + */ + interface Listener { + fun onCriteriaChanged() + } + + /** + * Adds a listener to receive future updates. {@link Listener#onCriteriaChanged} will be called + * immediately upon adding. + */ + fun addListener(listener: Listener) + + /** + * Removes a listener from receiving future updates. + */ + fun removeListener(listener: Listener) + + /** + * Returns whether all conditions have been met. + */ + fun conditionsMet(): Boolean +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt b/packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt new file mode 100644 index 000000000000..7228550e4e41 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace + +import android.app.smartspace.SmartspaceTarget + +/** + * {@link SmartspaceTargetFilter} defines a way to locally filter targets from inclusion. This + * should be used for filtering that isn't available further upstream. + */ +interface SmartspaceTargetFilter { + /** + * An interface implemented by clients to receive updates when the filtering criteria changes. + * When this happens, the client should refresh their target set. + */ + interface Listener { + fun onCriteriaChanged() + } + + /** + * Adds a listener to receive future updates. {@link Listener#onCriteriaChanged} will be + * invoked immediately after. + */ + fun addListener(listener: Listener) + + /** + * Removes listener from receiving future updates. + */ + fun removeListener(listener: Listener) + + /** + * Returns {@code true} if the {@link SmartspaceTarget} should be included in the current + * target set, {@code false} otherwise. + */ + fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt new file mode 100644 index 000000000000..1b74ac36ebf0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace.dagger + +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.smartspace.SmartspacePrecondition +import com.android.systemui.smartspace.SmartspaceTargetFilter +import com.android.systemui.smartspace.filters.LockscreenTargetFilter +import com.android.systemui.smartspace.preconditions.LockscreenPrecondition +import dagger.Binds +import dagger.BindsOptionalOf +import dagger.Module +import javax.inject.Named + +@Module(subcomponents = [SmartspaceViewComponent::class]) +abstract class SmartspaceModule { + @Module + companion object { + /** + * The BcSmartspaceDataProvider for dreams. + */ + const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin" + + /** + * The lockscreen smartspace target filter. + */ + const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter" + + /** + * The dream smartspace target filter. + */ + const val DREAM_SMARTSPACE_TARGET_FILTER = "dream_smartspace_target_filter" + + /** + * The precondition for dream smartspace + */ + const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition" + } + + @BindsOptionalOf + @Named(DREAM_SMARTSPACE_TARGET_FILTER) + abstract fun optionalDreamSmartspaceTargetFilter(): SmartspaceTargetFilter? + + @BindsOptionalOf + @Named(DREAM_SMARTSPACE_DATA_PLUGIN) + abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin? + + @Binds + @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER) + abstract fun provideLockscreenSmartspaceTargetFilter( + filter: LockscreenTargetFilter? + ): SmartspaceTargetFilter? + + @Binds + @Named(DREAM_SMARTSPACE_PRECONDITION) + abstract fun bindSmartspacePrecondition( + lockscreenPrecondition: LockscreenPrecondition? + ): SmartspacePrecondition? +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt new file mode 100644 index 000000000000..d3ae198e8e35 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace.dagger + +import android.app.PendingIntent +import android.content.Intent +import android.view.View +import android.view.ViewGroup +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.smartspace.dagger.SmartspaceViewComponent.SmartspaceViewModule.PLUGIN +import dagger.BindsInstance +import dagger.Module +import dagger.Provides +import dagger.Subcomponent +import javax.inject.Named + +@Subcomponent(modules = [SmartspaceViewComponent.SmartspaceViewModule::class]) +interface SmartspaceViewComponent { + @Subcomponent.Factory + interface Factory { + fun create( + @BindsInstance parent: ViewGroup, + @BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, + @BindsInstance onAttachListener: View.OnAttachStateChangeListener + ): SmartspaceViewComponent + } + + fun getView(): BcSmartspaceDataPlugin.SmartspaceView + + @Module + object SmartspaceViewModule { + const val PLUGIN = "plugin" + + @Provides + fun providesSmartspaceView( + activityStarter: ActivityStarter, + falsingManager: FalsingManager, + parent: ViewGroup, + @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, + onAttachListener: View.OnAttachStateChangeListener + ): + BcSmartspaceDataPlugin.SmartspaceView { + val ssView = plugin.getView(parent) + ssView.registerDataProvider(plugin) + + ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter { + override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) { + activityStarter.startActivity( + intent, + true, /* dismissShade */ + null, /* launch animator */ + showOnLockscreen + ) + } + + override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) { + if (showOnLockscreen) { + pi.send() + } else { + activityStarter.startPendingIntentDismissingKeyguard(pi) + } + } + }) + (ssView as View).addOnAttachStateChangeListener(onAttachListener) + ssView.setFalsingManager(falsingManager) + return ssView + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt b/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt new file mode 100644 index 000000000000..6ad490169c17 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace.filters + +import android.app.smartspace.SmartspaceTarget +import android.content.ContentResolver +import android.content.Context +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.settings.UserTracker +import com.android.systemui.smartspace.SmartspaceTargetFilter +import com.android.systemui.util.concurrency.Execution +import com.android.systemui.util.settings.SecureSettings +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * {@link SmartspaceTargetFilter} for smartspace targets that show above the lockscreen. + */ +class LockscreenTargetFilter @Inject constructor( + private val secureSettings: SecureSettings, + private val userTracker: UserTracker, + private val execution: Execution, + @Main private val handler: Handler, + private val contentResolver: ContentResolver, + @Main private val uiExecutor: Executor +) : SmartspaceTargetFilter { + private var listeners: MutableSet<SmartspaceTargetFilter.Listener> = mutableSetOf() + private var showSensitiveContentForCurrentUser = false + set(value) { + val existing = field + field = value + if (existing != field) { + listeners.forEach { it.onCriteriaChanged() } + } + } + private var showSensitiveContentForManagedUser = false + set(value) { + val existing = field + field = value + if (existing != field) { + listeners.forEach { it.onCriteriaChanged() } + } + } + + private val settingsObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + execution.assertIsMainThread() + updateUserContentSettings() + } + } + + private var managedUserHandle: UserHandle? = null + + override fun addListener(listener: SmartspaceTargetFilter.Listener) { + listeners.add(listener) + + if (listeners.size != 1) { + return + } + + userTracker.addCallback(userTrackerCallback, uiExecutor) + + contentResolver.registerContentObserver( + secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + true, + settingsObserver, + UserHandle.USER_ALL + ) + + updateUserContentSettings() + } + + override fun removeListener(listener: SmartspaceTargetFilter.Listener) { + listeners.remove(listener) + + if (listeners.isNotEmpty()) { + return + } + + userTracker.removeCallback(userTrackerCallback) + contentResolver.unregisterContentObserver(settingsObserver) + } + + override fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { + return when (t.userHandle) { + userTracker.userHandle -> { + !t.isSensitive || showSensitiveContentForCurrentUser + } + managedUserHandle -> { + // Really, this should be "if this managed profile is associated with the current + // active user", but we don't have a good way to check that, so instead we cheat: + // Only the primary user can have an associated managed profile, so only show + // content for the managed profile if the primary user is active + userTracker.userHandle.identifier == UserHandle.USER_SYSTEM && + (!t.isSensitive || showSensitiveContentForManagedUser) + } + else -> { + false + } + } + } + + private val userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + execution.assertIsMainThread() + updateUserContentSettings() + } + } + + private fun getWorkProfileUser(): UserHandle? { + for (userInfo in userTracker.userProfiles) { + if (userInfo.isManagedProfile) { + return userInfo.userHandle + } + } + return null + } + + private fun updateUserContentSettings() { + val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS + + showSensitiveContentForCurrentUser = + secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1 + + managedUserHandle = getWorkProfileUser() + val managedId = managedUserHandle?.identifier + if (managedId != null) { + showSensitiveContentForManagedUser = + secureSettings.getIntForUser(setting, 0, managedId) == 1 + } + + listeners.forEach { it.onCriteriaChanged() } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt new file mode 100644 index 000000000000..1302ec9dbc55 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace.preconditions + +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.smartspace.SmartspacePrecondition +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.util.concurrency.Execution +import javax.inject.Inject + +/** + * {@link LockscreenPrecondition} covers the conditions that must be met before Smartspace can be + * used over lockscreen. These conditions include the device being provisioned with a setup user + * and the Smartspace feature flag enabled. + */ +class LockscreenPrecondition @Inject constructor( + private val featureFlags: FeatureFlags, + private val deviceProvisionedController: DeviceProvisionedController, + private val execution: Execution +) : SmartspacePrecondition { + private var listeners = mutableSetOf<SmartspacePrecondition.Listener>() + + private val deviceProvisionedListener = + object : DeviceProvisionedController.DeviceProvisionedListener { + override fun onDeviceProvisionedChanged() { + updateDeviceReadiness() + } + + override fun onUserSetupChanged() { + updateDeviceReadiness() + } + } + + init { + deviceProvisionedController.addCallback(deviceProvisionedListener) + } + + var deviceReady: Boolean = false + private set + + init { + updateDeviceReadiness() + } + + private fun updateDeviceReadiness() { + if (deviceReady) { + return + } + + deviceReady = deviceProvisionedController.isDeviceProvisioned && + deviceProvisionedController.isCurrentUserSetup + + if (!deviceReady) { + return + } + + deviceProvisionedController.removeCallback(deviceProvisionedListener) + synchronized(listeners) { + listeners.forEach { it.onCriteriaChanged() } + } + } + + override fun addListener(listener: SmartspacePrecondition.Listener) { + synchronized(listeners) { + listeners += listener + } + // Always trigger a targeted callback upon addition of listener. + listener.onCriteriaChanged() + } + + override fun removeListener(listener: SmartspacePrecondition.Listener) { + synchronized(listeners) { + listeners -= listener + } + } + + override fun conditionsMet(): Boolean { + execution.assertIsMainThread() + return featureFlags.isEnabled(Flags.SMARTSPACE) && deviceReady + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 5adb9e55a9df..1ab03455cca2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -304,6 +304,7 @@ class LockscreenShadeTransitionController @Inject constructor( // override that forceApplyAmount = true // Reset the behavior. At this point the animation is already started + logger.logDragDownAmountReset() dragDownAmount = 0f forceApplyAmount = false } @@ -480,6 +481,7 @@ class LockscreenShadeTransitionController @Inject constructor( setDragDownAmountAnimated(fullTransitionDistanceByTap.toFloat(), delay = delay) { // End listener: // Reset + logger.logDragDownAmountReset() dragDownAmount = 0f forceApplyAmount = false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 267ee6d2d177..a0388de5150c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -259,6 +259,7 @@ class NotificationShadeDepthController @Inject constructor( addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { keyguardAnimator = null + wakeAndUnlockBlurRadius = 0f scheduleUpdate() } }) @@ -439,7 +440,7 @@ class NotificationShadeDepthController @Inject constructor( it.println("StatusBarWindowBlurController:") it.increaseIndent() it.println("shadeExpansion: $shadeExpansion") - it.println("shouldApplyShaeBlur: ${shouldApplyShadeBlur()}") + it.println("shouldApplyShadeBlur: ${shouldApplyShadeBlur()}") it.println("shadeAnimation: ${shadeAnimation.radius}") it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 369ef343a4e2..2baa0797b41f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -230,7 +230,13 @@ public class ShadeListBuilder implements Dumpable { mPipelineState.requireState(STATE_IDLE); mNotifSections.clear(); + NotifSectioner lastSection = null; for (NotifSectioner sectioner : sectioners) { + if (lastSection != null && lastSection.getBucket() > sectioner.getBucket()) { + throw new IllegalArgumentException("setSectioners with non contiguous sections " + + lastSection.getName() + " - " + lastSection.getBucket() + " & " + + sectioner.getName() + " - " + sectioner.getBucket()); + } final NotifSection section = new NotifSection(sectioner, mNotifSections.size()); final NotifComparator sectionComparator = section.getComparator(); mNotifSections.add(section); @@ -238,6 +244,7 @@ public class ShadeListBuilder implements Dumpable { if (sectionComparator != null) { sectionComparator.setInvalidationListener(this::onNotifComparatorInvalidated); } + lastSection = sectioner; } mNotifSections.add(new NotifSection(DEFAULT_SECTIONER, mNotifSections.size())); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 41b070635d4f..0df2162d3338 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -72,9 +72,9 @@ class HeadsUpCoordinator @Inject constructor( private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null private lateinit var mNotifPipeline: NotifPipeline private var mNow: Long = -1 - // notifs we've extended the lifetime for private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>() + private val mPostedEntries = LinkedHashMap<String, PostedEntry>() override fun attach(pipeline: NotifPipeline) { mNotifPipeline = pipeline @@ -101,10 +101,12 @@ class HeadsUpCoordinator @Inject constructor( return } // Process all non-group adds/updates - mPostedEntries.values.toList().forEach { posted -> - if (!posted.entry.sbn.isGroup) { - handlePostedEntry(posted, "non-group") - mPostedEntries.remove(posted.key) + mHeadsUpManager.modifyHuns { hunMutator -> + mPostedEntries.values.toList().forEach { posted -> + if (!posted.entry.sbn.isGroup) { + handlePostedEntry(posted, hunMutator, "non-group") + mPostedEntries.remove(posted.key) + } } } } @@ -114,10 +116,10 @@ class HeadsUpCoordinator @Inject constructor( * we know that stability and [NotifPromoter]s have been applied, so we can use the location of * notifications in this list to determine what kind of group alert behavior should happen. */ - fun onBeforeFinalizeFilter(list: List<ListEntry>) { + fun onBeforeFinalizeFilter(list: List<ListEntry>) = mHeadsUpManager.modifyHuns { hunMutator -> // Nothing to do if there are no other adds/updates if (mPostedEntries.isEmpty()) { - return + return@modifyHuns } // Calculate a bunch of information about the logical group and the locations of group // entries in the nearly-finalized shade list. These may be used in the per-group loop. @@ -138,13 +140,17 @@ class HeadsUpCoordinator @Inject constructor( // If there is no logical summary, then there is no alert to transfer if (logicalSummary == null) { - postedEntries.forEach { handlePostedEntry(it, "logical-summary-missing") } + postedEntries.forEach { + handlePostedEntry(it, hunMutator, scenario = "logical-summary-missing") + } return@forEach } // If summary isn't wanted to be heads up, then there is no alert to transfer if (!isGoingToShowHunStrict(logicalSummary)) { - postedEntries.forEach { handlePostedEntry(it, "logical-summary-not-alerting") } + postedEntries.forEach { + handlePostedEntry(it, hunMutator, scenario = "logical-summary-not-alerting") + } return@forEach } @@ -177,7 +183,9 @@ class HeadsUpCoordinator @Inject constructor( // If there is no child to receive the parent alert, then just handle the posted entries // and return. if (childToReceiveParentAlert == null) { - postedEntries.forEach { handlePostedEntry(it, "no-transfer-target") } + postedEntries.forEach { + handlePostedEntry(it, hunMutator, scenario = "no-transfer-target") + } return@forEach } @@ -189,51 +197,66 @@ class HeadsUpCoordinator @Inject constructor( if (!isSummaryAttached) { val summaryUpdateForRemoval = summaryUpdate?.also { it.shouldHeadsUpEver = false - } ?: PostedEntry(logicalSummary, - wasAdded = false, - wasUpdated = false, - shouldHeadsUpEver = false, - shouldHeadsUpAgain = false, - isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key), - isBinding = isEntryBinding(logicalSummary), + } ?: PostedEntry( + logicalSummary, + wasAdded = false, + wasUpdated = false, + shouldHeadsUpEver = false, + shouldHeadsUpAgain = false, + isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key), + isBinding = isEntryBinding(logicalSummary), ) // If we transfer the alert and the summary isn't even attached, that means we // should ensure the summary is no longer alerting, so we remove it here. - handlePostedEntry(summaryUpdateForRemoval, "detached-summary-remove-alert") - } else if (summaryUpdate!=null) { - mLogger.logPostedEntryWillNotEvaluate(summaryUpdate, "attached-summary-transferred") + handlePostedEntry( + summaryUpdateForRemoval, + hunMutator, + scenario = "detached-summary-remove-alert") + } else if (summaryUpdate != null) { + mLogger.logPostedEntryWillNotEvaluate( + summaryUpdate, + reason = "attached-summary-transferred") } // Handle all posted entries -- if the child receiving the parent's alert is in the // list, then set its flags to ensure it alerts. var didAlertChildToReceiveParentAlert = false postedEntries.asSequence() - .filter { it.key != logicalSummary.key } - .forEach { postedEntry -> - if (childToReceiveParentAlert.key == postedEntry.key) { - // Update the child's posted update so that it - postedEntry.shouldHeadsUpEver = true - postedEntry.shouldHeadsUpAgain = true - handlePostedEntry(postedEntry, "child-alert-transfer-target-$targetType") - didAlertChildToReceiveParentAlert = true - } else { - handlePostedEntry(postedEntry, "child-alert-non-target") + .filter { it.key != logicalSummary.key } + .forEach { postedEntry -> + if (childToReceiveParentAlert.key == postedEntry.key) { + // Update the child's posted update so that it + postedEntry.shouldHeadsUpEver = true + postedEntry.shouldHeadsUpAgain = true + handlePostedEntry( + postedEntry, + hunMutator, + scenario = "child-alert-transfer-target-$targetType") + didAlertChildToReceiveParentAlert = true + } else { + handlePostedEntry( + postedEntry, + hunMutator, + scenario = "child-alert-non-target") + } } - } // If the child receiving the alert was not updated on this tick (which can happen in a // standard alert transfer scenario), then construct an update so that we can apply it. if (!didAlertChildToReceiveParentAlert) { val posted = PostedEntry( - childToReceiveParentAlert, - wasAdded = false, - wasUpdated = false, - shouldHeadsUpEver = true, - shouldHeadsUpAgain = true, - isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key), - isBinding = isEntryBinding(childToReceiveParentAlert), + childToReceiveParentAlert, + wasAdded = false, + wasUpdated = false, + shouldHeadsUpEver = true, + shouldHeadsUpAgain = true, + isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key), + isBinding = isEntryBinding(childToReceiveParentAlert), ) - handlePostedEntry(posted, "non-posted-child-alert-transfer-target-$targetType") + handlePostedEntry( + posted, + hunMutator, + scenario = "non-posted-child-alert-transfer-target-$targetType") } } // After this method runs, all posted entries should have been handled (or skipped). @@ -292,9 +315,7 @@ class HeadsUpCoordinator @Inject constructor( } } - private val mPostedEntries = LinkedHashMap<String, PostedEntry>() - - fun handlePostedEntry(posted: PostedEntry, scenario: String) { + private fun handlePostedEntry(posted: PostedEntry, hunMutator: HunMutator, scenario: String) { mLogger.logPostedEntryWillEvaluate(posted, scenario) if (posted.wasAdded) { if (posted.shouldHeadsUpEver) { @@ -308,12 +329,12 @@ class HeadsUpCoordinator @Inject constructor( // If alerting, we need to post an update. Otherwise we're still binding, // and we can just let that finish. if (posted.isAlerting) { - mHeadsUpManager.updateNotification(posted.key, posted.shouldHeadsUpAgain) + hunMutator.updateNotification(posted.key, posted.shouldHeadsUpAgain) } } else { if (posted.isAlerting) { // We don't want this to be interrupting anymore, let's remove it - mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/) + hunMutator.removeNotification(posted.key, false /*removeImmediately*/) } else { // Don't let the bind finish cancelHeadsUpBind(posted.entry) @@ -366,7 +387,7 @@ class HeadsUpCoordinator @Inject constructor( val shouldHeadsUpAgain = shouldHunAgain(entry) val isAlerting = mHeadsUpManager.isAlerting(entry.key) val isBinding = isEntryBinding(entry) - mPostedEntries.compute(entry.key) { _, value -> + val posted = mPostedEntries.compute(entry.key) { _, value -> value?.also { update -> update.wasUpdated = true update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver @@ -383,6 +404,18 @@ class HeadsUpCoordinator @Inject constructor( isBinding = isBinding, ) } + // Handle cancelling alerts here, rather than in the OnBeforeFinalizeFilter, so that + // work can be done before the ShadeListBuilder is run. This prevents re-entrant + // behavior between this Coordinator, HeadsUpManager, and VisualStabilityManager. + if (posted?.shouldHeadsUpEver == false) { + if (posted.isAlerting) { + // We don't want this to be interrupting anymore, let's remove it + mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/) + } else if (posted.isBinding) { + // Don't let the bind finish + cancelHeadsUpBind(posted.entry) + } + } } /** @@ -543,3 +576,43 @@ private enum class GroupLocation { Detached, Isolated, Summary, Child } private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation = getOrDefault(key, GroupLocation.Detached) + +/** + * Invokes the given block with a [HunMutator] that defers all HUN removals. This ensures that the + * HeadsUpManager is notified of additions before removals, which prevents a glitch where the + * HeadsUpManager temporarily believes that nothing is alerting, causing bad re-entrant behavior. + */ +private fun <R> HeadsUpManager.modifyHuns(block: (HunMutator) -> R): R { + val mutator = HunMutatorImpl(this) + return block(mutator).also { mutator.commitModifications() } +} + +/** Mutates the HeadsUp state of notifications. */ +private interface HunMutator { + fun updateNotification(key: String, alert: Boolean) + fun removeNotification(key: String, releaseImmediately: Boolean) +} + +/** + * [HunMutator] implementation that defers removing notifications from the HeadsUpManager until + * after additions/updates. + */ +private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator { + private val deferred = mutableListOf<Pair<String, Boolean>>() + + override fun updateNotification(key: String, alert: Boolean) { + headsUpManager.updateNotification(key, alert) + } + + override fun removeNotification(key: String, releaseImmediately: Boolean) { + val args = Pair(key, releaseImmediately) + deferred.add(args) + } + + fun commitModifications() { + deferred.forEach { (key, releaseImmediately) -> + headsUpManager.removeNotification(key, releaseImmediately) + } + deferred.clear() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index c35b77cfb4e0..6db544c77f87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -57,6 +57,7 @@ class NodeSpecBuilder( var currentSection: NotifSection? = null val prevSections = mutableSetOf<NotifSection?>() + var lastSection: NotifSection? = null val showHeaders = sectionHeaderVisibilityProvider.sectionHeadersVisible val sectionOrder = mutableListOf<NotifSection?>() val sectionHeaders = mutableMapOf<NotifSection?, NodeController?>() @@ -65,6 +66,14 @@ class NodeSpecBuilder( for (entry in notifList) { val section = entry.section!! + lastSection?.let { + if (it.bucket > section.bucket) { + throw IllegalStateException("buildNodeSpec with non contiguous section " + + "buckets ${it.sectioner.name} - ${it.bucket} & " + + "${it.sectioner.name} - ${it.bucket}") + } + } + lastSection = section if (prevSections.contains(section)) { throw java.lang.RuntimeException("Section ${section.label} has been duplicated") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 7df8e7df486e..6bbecc8438bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1233,10 +1233,6 @@ public class NotificationStackScrollLayoutController { mView.forceNoOverlappingRendering(force); } - public void setTranslationX(float translation) { - mView.setTranslationX(translation); - } - public void setExpandingVelocity(float velocity) { mView.setExpandingVelocity(velocity); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index fe637c14ee33..4bf944ae13c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -20,6 +20,8 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD; import android.annotation.IntDef; import android.content.res.Resources; +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricSourceType; import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; @@ -344,7 +346,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } @Override - public void onBiometricAcquired(BiometricSourceType biometricSourceType) { + public void onBiometricAcquired(BiometricSourceType biometricSourceType, + int acquireInfo) { + if (BiometricSourceType.FINGERPRINT == biometricSourceType + && acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) { + return; + } else if (BiometricSourceType.FACE == biometricSourceType + && acquireInfo != BiometricFaceConstants.FACE_ACQUIRED_GOOD) { + return; + } Trace.beginSection("BiometricUnlockController#onBiometricAcquired"); releaseBiometricWakeLock(); if (isWakeAndUnlock()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 84adc5679a81..a7f950eaf167 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -663,7 +663,6 @@ public class CentralSurfaces extends CoreStartable implements protected final BatteryController mBatteryController; protected boolean mPanelExpanded; private UiModeManager mUiModeManager; - protected boolean mIsKeyguard; private LogMaker mStatusBarStateLog; protected final NotificationIconAreaController mNotificationIconAreaController; @Nullable private View mAmbientIndicationContainer; @@ -1142,7 +1141,7 @@ public class CentralSurfaces extends CoreStartable implements } if (leaveOpen) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - if (mIsKeyguard) { + if (mKeyguardStateController.isShowing()) { // When device state changes on keyguard we don't want to keep the state of // the shade and instead we open clean state of keyguard with shade closed. // Normally some parts of QS state (like expanded/collapsed) are persisted and @@ -2890,7 +2889,9 @@ public class CentralSurfaces extends CoreStartable implements // late in the transition, so we also allow the device to start dozing once the screen has // turned off fully. boolean keyguardForDozing = mDozeServiceHost.getDozingRequested() - && (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard)); + && (!mDeviceInteractive || (isGoingToSleep() + && (isScreenFullyOff() + || (mKeyguardStateController.isShowing() && !isOccluded())))); boolean isWakingAndOccluded = isOccluded() && isWaking(); boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested() || keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded; @@ -2923,7 +2924,6 @@ public class CentralSurfaces extends CoreStartable implements public void showKeyguardImpl() { Trace.beginSection("CentralSurfaces#showKeyguard"); - mIsKeyguard = true; // In case we're locking while a smartspace transition is in progress, reset it. mKeyguardUnlockAnimationController.resetSmartspaceTransition(); if (mKeyguardStateController.isLaunchTransitionFadingAway()) { @@ -3044,7 +3044,6 @@ public class CentralSurfaces extends CoreStartable implements * @return true if we would like to stay in the shade, false if it should go away entirely */ public boolean hideKeyguardImpl(boolean forceStateChange) { - mIsKeyguard = false; Trace.beginSection("CentralSurfaces#hideKeyguard"); boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); int previousState = mStatusBarStateController.getState(); @@ -3771,7 +3770,7 @@ public class CentralSurfaces extends CoreStartable implements }); } else if (mDozing && !unlocking) { mScrimController.transitionTo(ScrimState.AOD); - } else if (mIsKeyguard && !unlocking) { + } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) { mScrimController.transitionTo(ScrimState.KEYGUARD); } else { mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 541aeab22a80..dc1af362ec23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -32,7 +32,6 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.admin.DevicePolicyManager; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -1109,8 +1108,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL Intent intent = mQRCodeScannerController.getIntent(); if (intent != null) { try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { + ActivityTaskManager.getService().startActivityAsUser( + null, getContext().getBasePackageName(), + getContext().getAttributionTag(), intent, + intent.resolveTypeIfNeeded(getContext().getContentResolver()), + null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null, + UserHandle.CURRENT.getIdentifier()); + } catch (RemoteException e) { // This is unexpected. Nonetheless, just log the error and prevent the UI from // crashing Log.e(TAG, "Unexpected intent: " + intent diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt index 868efa027f40..02b235493715 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt @@ -44,7 +44,7 @@ class LSShadeTransitionLogger @Inject constructor( fun logDragDownAborted() { buffer.log(TAG, LogLevel.INFO, {}, { - "The drag down was reset" + "The drag down was aborted and reset to 0f." }) } @@ -82,6 +82,12 @@ class LSShadeTransitionLogger @Inject constructor( LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN) } + fun logDragDownAmountReset() { + buffer.log(TAG, LogLevel.DEBUG, {}, { + "The drag down amount has been reset to 0f." + }) + } + fun logDefaultGoToFullShadeAnimation(delay: Long) { buffer.log(TAG, LogLevel.DEBUG, { long1 = delay diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index cc8a70388ed0..0a2ea4cfe11a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -18,13 +18,9 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.view.View.GONE; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM; import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; -import static androidx.constraintlayout.widget.ConstraintSet.START; -import static androidx.constraintlayout.widget.ConstraintSet.TOP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; @@ -1080,14 +1076,10 @@ public class NotificationPanelViewController extends PanelViewController { public void updateResources() { mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext()); - mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext()); mSplitShadeNotificationsScrimMarginBottom = mResources.getDimensionPixelSize( R.dimen.split_shade_notifications_scrim_margin_bottom); - int panelMarginHorizontal = mResources.getDimensionPixelSize( - R.dimen.notification_panel_margin_horizontal); - final boolean newShouldUseSplitNotificationShade = Utils.shouldUseSplitNotificationShade(mResources); final boolean splitNotificationShadeChanged = @@ -1097,49 +1089,12 @@ public class NotificationPanelViewController extends PanelViewController { if (mQs != null) { mQs.setInSplitShade(mShouldUseSplitNotificationShade); } - - int notificationsBottomMargin = mResources.getDimensionPixelSize( - R.dimen.notification_panel_margin_bottom); + mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext()); int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight : mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top); mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade); - - // To change the constraints at runtime, all children of the ConstraintLayout must have ids - ensureAllViewsHaveIds(mNotificationContainerParent); - ConstraintSet constraintSet = new ConstraintSet(); - constraintSet.clone(mNotificationContainerParent); - int statusViewMarginHorizontal = mResources.getDimensionPixelSize( - R.dimen.status_view_margin_horizontal); - constraintSet.setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal); - constraintSet.setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal); - if (mShouldUseSplitNotificationShade) { - // width = 0 to take up all available space within constraints - constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END); - constraintSet.connect( - R.id.notification_stack_scroller, START, - R.id.qs_edge_guideline, START); - constraintSet.constrainHeight(R.id.split_shade_status_bar, mSplitShadeStatusBarHeight); - } else { - constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END); - constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START); - if (mUseCombinedQSHeaders) { - constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT); - } - } - constraintSet.setMargin(R.id.notification_stack_scroller, START, - mShouldUseSplitNotificationShade ? 0 : panelMarginHorizontal); - constraintSet.setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal); - constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin); - constraintSet.setMargin(R.id.notification_stack_scroller, BOTTOM, - notificationsBottomMargin); - constraintSet.setMargin(R.id.qs_frame, START, panelMarginHorizontal); - constraintSet.setMargin(R.id.qs_frame, END, - mShouldUseSplitNotificationShade ? 0 : panelMarginHorizontal); - constraintSet.setMargin(R.id.qs_frame, TOP, topMargin); - constraintSet.applyTo(mNotificationContainerParent); mAmbientState.setStackTopMargin(topMargin); - mNotificationsQSContainerController.updateMargins(); - mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade); + mNotificationsQSContainerController.updateResources(); updateKeyguardStatusViewAlignment(/* animate= */false); @@ -1150,15 +1105,6 @@ public class NotificationPanelViewController extends PanelViewController { } } - private static void ensureAllViewsHaveIds(ViewGroup parentView) { - for (int i = 0; i < parentView.getChildCount(); i++) { - View childView = parentView.getChildAt(i); - if (childView.getId() == View.NO_ID) { - childView.setId(View.generateViewId()); - } - } - } - private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) { View view = mView.findViewById(viewId); if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt index ebb09b1af5e9..e4161a37e52e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt @@ -1,6 +1,15 @@ package com.android.systemui.statusbar.phone +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowInsets +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.R import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -10,6 +19,7 @@ import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.shared.system.QuickStepContract +import com.android.systemui.util.Utils import com.android.systemui.util.ViewController import java.util.function.Consumer import javax.inject.Inject @@ -28,23 +38,20 @@ class NotificationsQSContainerController @Inject constructor( mView.invalidate() } } - var splitShadeEnabled = false - set(value) { - if (field != value) { - field = value - // in case device configuration changed while showing QS details/customizer - updateBottomSpacing() - } - } - + private var splitShadeEnabled = false private var isQSDetailShowing = false private var isQSCustomizing = false private var isQSCustomizerAnimating = false + private var splitShadeStatusBarHeight = 0 private var notificationsBottomMargin = 0 private var scrimShadeBottomMargin = 0 private var bottomStableInsets = 0 private var bottomCutoutInsets = 0 + private var panelMarginHorizontal = 0 + private var topMargin = 0 + + private val useCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS) private var isGestureNavigation = true private var taskbarVisible = false @@ -68,7 +75,6 @@ class NotificationsQSContainerController @Inject constructor( } public override fun onViewAttached() { - updateMargins() updateResources() overviewProxyService.addCallback(taskbarVisibilityListener) mView.setInsetsChangedListener(windowInsetsListener) @@ -83,7 +89,27 @@ class NotificationsQSContainerController @Inject constructor( mView.setConfigurationChangedListener(null) } - private fun updateResources() { + fun updateResources() { + val newSplitShadeEnabled = Utils.shouldUseSplitNotificationShade(resources) + val splitShadeEnabledChanged = newSplitShadeEnabled != splitShadeEnabled + splitShadeEnabled = newSplitShadeEnabled + notificationsBottomMargin = resources.getDimensionPixelSize( + R.dimen.notification_panel_margin_bottom) + splitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(context) + panelMarginHorizontal = resources.getDimensionPixelSize( + R.dimen.notification_panel_margin_horizontal) + topMargin = if (splitShadeEnabled) { + splitShadeStatusBarHeight + } else { + resources.getDimensionPixelSize(R.dimen.notification_panel_margin_top) + } + updateConstraints() + if (splitShadeEnabledChanged) { + // Let's do it at the end when all margins/paddings were already applied. + // We need to updateBottomSpacing() in case device configuration changed while showing + // QS details/customizer + updateBottomSpacing() + } val previousScrimShadeBottomMargin = scrimShadeBottomMargin scrimShadeBottomMargin = resources.getDimensionPixelSize( R.dimen.split_shade_notifications_scrim_margin_bottom @@ -94,15 +120,6 @@ class NotificationsQSContainerController @Inject constructor( } } - /** - * Update the notification bottom margin. - * - * Will not call updateBottomSpacing - */ - fun updateMargins() { - notificationsBottomMargin = mView.defaultNotificationsMarginBottom - } - override fun setCustomizerAnimating(animating: Boolean) { if (isQSCustomizerAnimating != animating) { isQSCustomizerAnimating = animating @@ -178,4 +195,66 @@ class NotificationsQSContainerController @Inject constructor( } return containerPadding to stackScrollMargin } -}
\ No newline at end of file + + fun updateConstraints() { + // To change the constraints at runtime, all children of the ConstraintLayout must have ids + ensureAllViewsHaveIds(mView) + val constraintSet = ConstraintSet() + constraintSet.clone(mView) + setKeyguardStatusViewConstraints(constraintSet) + setQsConstraints(constraintSet) + setNotificationsConstraints(constraintSet) + setSplitShadeStatusBarConstraints(constraintSet) + mView.applyConstraints(constraintSet) + } + + private fun setSplitShadeStatusBarConstraints(constraintSet: ConstraintSet) { + if (splitShadeEnabled) { + constraintSet.constrainHeight(R.id.split_shade_status_bar, splitShadeStatusBarHeight) + } else { + if (useCombinedQSHeaders) { + constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT) + } + } + } + + private fun setNotificationsConstraints(constraintSet: ConstraintSet) { + val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID + constraintSet.apply { + connect(R.id.notification_stack_scroller, START, startConstraintId, START) + setMargin(R.id.notification_stack_scroller, START, + if (splitShadeEnabled) 0 else panelMarginHorizontal) + setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal) + setMargin(R.id.notification_stack_scroller, TOP, topMargin) + setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin) + } + } + + private fun setQsConstraints(constraintSet: ConstraintSet) { + val endConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID + constraintSet.apply { + connect(R.id.qs_frame, END, endConstraintId, END) + setMargin(R.id.qs_frame, START, panelMarginHorizontal) + setMargin(R.id.qs_frame, END, if (splitShadeEnabled) 0 else panelMarginHorizontal) + setMargin(R.id.qs_frame, TOP, topMargin) + } + } + + private fun setKeyguardStatusViewConstraints(constraintSet: ConstraintSet) { + val statusViewMarginHorizontal = resources.getDimensionPixelSize( + R.dimen.status_view_margin_horizontal) + constraintSet.apply { + setMargin(R.id.keyguard_status_view, START, statusViewMarginHorizontal) + setMargin(R.id.keyguard_status_view, END, statusViewMarginHorizontal) + } + } + + private fun ensureAllViewsHaveIds(parentView: ViewGroup) { + for (i in 0 until parentView.childCount) { + val childView = parentView.getChildAt(i) + if (childView.id == View.NO_ID) { + childView.id = View.generateViewId() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index c2b5f5657fb0..7caea06e6359 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -26,6 +26,7 @@ import android.view.WindowInsets; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; import com.android.systemui.R; import com.android.systemui.fragments.FragmentHostManager; @@ -123,10 +124,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } } - public int getDefaultNotificationsMarginBottom() { - return ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin; - } - public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) { mInsetsChangedListener = onInsetsChangedListener; } @@ -197,4 +194,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } } + public void applyConstraints(ConstraintSet constraintSet) { + constraintSet.applyTo(this); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index a3c795f36d5d..419661b766d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -773,7 +773,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) { // We're unoccluding the keyguard and don't want to have a bright flash. - mNotificationsAlpha = mScrimBehindAlphaKeyguard; + mNotificationsAlpha = ScrimState.KEYGUARD.getNotifAlpha(); mNotificationsTint = ScrimState.KEYGUARD.getNotifTint(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java index ad47e2bc44a8..01fe8657fe47 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy; +import android.annotation.WorkerThread; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -219,6 +220,7 @@ public class FlashlightControllerImpl implements FlashlightController { new CameraManager.TorchCallback() { @Override + @WorkerThread public void onTorchModeUnavailable(String cameraId) { if (TextUtils.equals(cameraId, mCameraId)) { setCameraAvailable(false); @@ -229,6 +231,7 @@ public class FlashlightControllerImpl implements FlashlightController { } @Override + @WorkerThread public void onTorchModeChanged(String cameraId, boolean enabled) { if (TextUtils.equals(cameraId, mCameraId)) { setCameraAvailable(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 2cdbabde8115..e8bf89a6a90a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -267,7 +267,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize); } - Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar); + Drawable bg = mContext.getDrawable(R.drawable.user_avatar_bg); drawable = new LayerDrawable(new Drawable[]{bg, drawable}); return drawable; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index e6306e5cdd3f..03ab888d1253 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -540,7 +540,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); - Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar); + Drawable bg = mContext.getDrawable(R.drawable.user_avatar_bg); drawable = new LayerDrawable(new Drawable[]{bg, drawable}); return drawable; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index ddc907666f1c..763f0417cac8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -67,6 +67,7 @@ import com.android.systemui.R; import com.android.systemui.SystemUISecondaryUserService; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -122,6 +123,7 @@ public class UserSwitcherController implements Dumpable { protected final Handler mHandler; private final ActivityStarter mActivityStarter; private final BroadcastDispatcher mBroadcastDispatcher; + private final BroadcastSender mBroadcastSender; private final TelephonyListenerManager mTelephonyListenerManager; private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; @@ -165,6 +167,7 @@ public class UserSwitcherController implements Dumpable { @Main Handler handler, ActivityStarter activityStarter, BroadcastDispatcher broadcastDispatcher, + BroadcastSender broadcastSender, UiEventLogger uiEventLogger, FalsingManager falsingManager, TelephonyListenerManager telephonyListenerManager, @@ -179,6 +182,7 @@ public class UserSwitcherController implements Dumpable { mActivityManager = activityManager; mUserTracker = userTracker; mBroadcastDispatcher = broadcastDispatcher; + mBroadcastSender = broadcastSender; mTelephonyListenerManager = telephonyListenerManager; mUiEventLogger = uiEventLogger; mFalsingManager = falsingManager; @@ -982,9 +986,9 @@ public class UserSwitcherController implements Dumpable { protected static Drawable getIconDrawable(Context context, UserRecord item) { int iconRes; if (item.isAddUser) { - iconRes = R.drawable.ic_account_circle; - } else if (item.isGuest) { iconRes = R.drawable.ic_account_circle_filled; + } else if (item.isGuest) { + iconRes = R.drawable.ic_account_circle; } else if (item.isAddSupervisedUser) { iconRes = R.drawable.ic_add_supervised_user; } else { @@ -1194,7 +1198,7 @@ public class UserSwitcherController implements Dumpable { } // Use broadcast instead of ShadeController, as this dialog may have started in // another process and normal dagger bindings are not available - getContext().sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + mBroadcastSender.closeSystemDialogs(); getContext().startActivityAsUser( CreateUserActivity.createIntentForStart(getContext()), mUserTracker.getUserHandle()); diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 71d8e3344937..7e3bce589f7e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -199,10 +199,13 @@ public class Utils { /** * Gets the {@link R.dimen#split_shade_header_height}. * - * Currently, it's the same as {@link com.android.internal.R.dimen#quick_qs_offset_height}. + * It should be fine to not ignore cutouts as split shade might not want to react to them: + * for split shade header, which is only on bigger screens, either cutout won't be a problem + * (it's usually centered and in split shade that's likely empty area) or we probably want to + * handle it differently. */ public static int getSplitShadeStatusBarHeight(Context context) { - return SystemBarUtils.getQuickQsOffsetHeight(context); + return context.getResources().getDimensionPixelSize(R.dimen.split_shade_header_height); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt new file mode 100644 index 000000000000..214fd4d28398 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt @@ -0,0 +1,277 @@ +package com.android.systemui.animation + +import android.animation.ObjectAnimator +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertNotNull +import junit.framework.Assert.assertNull +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class ViewBoundAnimatorTest : SysuiTestCase() { + companion object { + private const val TEST_DURATION = 1000L + private val TEST_INTERPOLATOR = Interpolators.LINEAR + } + + private lateinit var rootView: LinearLayout + + @Before + fun setUp() { + rootView = LinearLayout(mContext) + } + + @After + fun tearDown() { + ViewBoundAnimator.stopAnimating(rootView) + } + + @Test + fun respectsAnimationParameters() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animate( + rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION + ) + rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + val animator = rootView.getTag(R.id.tag_animator) as ObjectAnimator + assertEquals(animator.interpolator, TEST_INTERPOLATOR) + assertEquals(animator.duration, TEST_DURATION) + } + + @Test + fun animatesFromStartToEnd() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animate(rootView) + // Change all bounds. + rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + // The initial values should be those of the previous layout. + checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + // The end values should be those of the latest layout. + checkBounds(rootView, l = 0, t = 15, r = 70, b = 80) + } + + @Test + fun animatesSuccessiveLayoutChanges() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animate(rootView) + // Change all bounds. + rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 0, t = 15, r = 70, b = 80) + + // Change only top and right. + rootView.layout(0 /* l */, 20 /* t */, 60 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 0, t = 20, r = 60, b = 80) + + // Change all bounds again. + rootView.layout(5 /* l */, 25 /* t */, 55 /* r */, 95 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 5, t = 25, r = 55, b = 95) + } + + @Test + fun animatesFromPreviousAnimationProgress() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR) + // Change all bounds. + rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + advanceAnimation(rootView, fraction = 0.5f) + checkBounds(rootView, l = 5, t = 15, r = 60, b = 65) + + // Change all bounds again. + rootView.layout(25 /* l */, 25 /* t */, 55 /* r */, 60 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 5, t = 15, r = 60, b = 65) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 25, t = 25, r = 55, b = 60) + } + + @Test + fun animatesRootAndChildren() { + val firstChild = View(mContext) + rootView.addView(firstChild) + val secondChild = View(mContext) + rootView.addView(secondChild) + rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */) + firstChild.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */) + secondChild.layout(100 /* l */, 0 /* t */, 150 /* r */, 100 /* b */) + + ViewBoundAnimator.animate(rootView) + // Change all bounds. + rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */) + firstChild.layout(10 /* l */, 20 /* t */, 150 /* r */, 120 /* b */) + secondChild.layout(150 /* l */, 20 /* t */, 200 /* r */, 120 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + // The initial values should be those of the previous layout. + checkBounds(rootView, l = 0, t = 0, r = 150, b = 100) + checkBounds(firstChild, l = 0, t = 0, r = 100, b = 100) + checkBounds(secondChild, l = 100, t = 0, r = 150, b = 100) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + // The end values should be those of the latest layout. + checkBounds(rootView, l = 10, t = 20, r = 200, b = 120) + checkBounds(firstChild, l = 10, t = 20, r = 150, b = 120) + checkBounds(secondChild, l = 150, t = 20, r = 200, b = 120) + } + + @Test + fun doesNotAnimateInvisibleViews() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animate(rootView) + // GONE. + rootView.visibility = View.GONE + rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */) + + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 0, t = 15, r = 55, b = 80) + + // INVISIBLE. + rootView.visibility = View.INVISIBLE + rootView.layout(0 /* l */, 20 /* t */, 0 /* r */, 20 /* b */) + } + + @Test + fun doesNotAnimateUnchangingBounds() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animate(rootView) + // No bounds are changed. + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) + + // Change only right and bottom. + rootView.layout(10 /* l */, 10 /* t */, 70 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 10, t = 10, r = 70, b = 80) + } + + @Test + fun doesNotAnimateExcludedBounds() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animate( + rootView, + bounds = setOf(ViewBoundAnimator.Bound.LEFT, ViewBoundAnimator.Bound.TOP), + interpolator = TEST_INTERPOLATOR + ) + // Change all bounds. + rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + advanceAnimation(rootView, 0.5f) + checkBounds(rootView, l = 5, t = 15, r = 70, b = 80) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 0, t = 20, r = 70, b = 80) + } + + @Test + fun stopsAnimatingAfterSingleLayout() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animateNextUpdate(rootView) + // Change all bounds. + rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 0, t = 15, r = 70, b = 80) + + // Change all bounds again. + rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */) + + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) + } + + @Test + fun stopsAnimatingWhenInstructed() { + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) + + ViewBoundAnimator.animate(rootView) + // Change all bounds. + rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */) + + assertNotNull(rootView.getTag(R.id.tag_animator)) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 0, t = 15, r = 70, b = 80) + + ViewBoundAnimator.stopAnimating(rootView) + // Change all bounds again. + rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */) + + assertNull(rootView.getTag(R.id.tag_animator)) + checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) + } + + private fun checkBounds(v: View, l: Int, t: Int, r: Int, b: Int) { + assertEquals(l, v.left) + assertEquals(t, v.top) + assertEquals(r, v.right) + assertEquals(b, v.bottom) + } + + private fun advanceAnimation(rootView: View, fraction: Float) { + (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.setCurrentFraction(fraction) + + if (rootView is ViewGroup) { + for (i in 0 until rootView.childCount) { + advanceAnimation(rootView.getChildAt(i), fraction) + } + } + } + + private fun endAnimation(rootView: View) { + (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.end() + + if (rootView is ViewGroup) { + for (i in 0 until rootView.childCount) { + endAnimation(rootView.getChildAt(i)) + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt new file mode 100644 index 000000000000..fbd2c918648a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.broadcast + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.util.wakelock.WakeLockFake +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class BroadcastSenderTest : SysuiTestCase() { + + @Mock + private lateinit var mockContext: Context + + private lateinit var broadcastSender: BroadcastSender + private lateinit var executor: FakeExecutor + private lateinit var wakeLock: WakeLockFake + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + executor = FakeExecutor(FakeSystemClock()) + wakeLock = WakeLockFake() + val wakeLockBuilder = WakeLockFake.Builder(mContext) + wakeLockBuilder.setWakeLock(wakeLock) + broadcastSender = BroadcastSender(mockContext, wakeLockBuilder, executor) + } + + @Test + fun sendBroadcast_dispatchesWithWakelock() { + val intent = Intent(Intent.ACTION_VIEW) + broadcastSender.sendBroadcast(intent) + + runExecutorAssertingWakelock { + verify(mockContext).sendBroadcast(intent) + } + } + + @Test + fun sendBroadcastWithPermission_dispatchesWithWakelock() { + val intent = Intent(Intent.ACTION_VIEW) + val permission = "Permission" + broadcastSender.sendBroadcast(intent, permission) + + runExecutorAssertingWakelock { + verify(mockContext).sendBroadcast(intent, permission) + } + } + + @Test + fun sendBroadcastAsUser_dispatchesWithWakelock() { + val intent = Intent(Intent.ACTION_VIEW) + broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL) + + runExecutorAssertingWakelock { + verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL) + } + } + + @Test + fun sendBroadcastAsUserWithPermission_dispatchesWithWakelock() { + val intent = Intent(Intent.ACTION_VIEW) + val permission = "Permission" + broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL, permission) + + runExecutorAssertingWakelock { + verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL, permission) + } + } + + @Test + fun sendBroadcastAsUserWithPermissionAndOptions_dispatchesWithWakelock() { + val intent = Intent(Intent.ACTION_VIEW) + val permission = "Permission" + val options = Bundle() + options.putString("key", "value") + + broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL, permission, options) + + runExecutorAssertingWakelock { + verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL, permission, options) + } + } + + @Test + fun sendBroadcastAsUserWithPermissionAndAppOp_dispatchesWithWakelock() { + val intent = Intent(Intent.ACTION_VIEW) + val permission = "Permission" + + broadcastSender.sendBroadcastAsUser(intent, UserHandle.ALL, permission, 12) + + runExecutorAssertingWakelock { + verify(mockContext).sendBroadcastAsUser(intent, UserHandle.ALL, permission, 12) + } + } + + @Test + fun sendCloseSystemDialogs_dispatchesWithWakelock() { + val intentCaptor = ArgumentCaptor.forClass(Intent::class.java) + + broadcastSender.closeSystemDialogs() + + runExecutorAssertingWakelock { + verify(mockContext).sendBroadcast(intentCaptor.capture()) + assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + } + } + + private fun runExecutorAssertingWakelock(verification: () -> Unit) { + assertThat(wakeLock.isHeld).isTrue() + executor.runAllReady() + verification.invoke() + assertThat(wakeLock.isHeld).isFalse() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt index da25c6202b21..49eaf8239ca0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt @@ -23,6 +23,7 @@ import android.provider.Settings import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.VibratorHelper @@ -62,6 +63,8 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock + private lateinit var broadcastSender: BroadcastSender + @Mock private lateinit var taskViewFactory: Optional<TaskViewFactory> @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var cvh: ControlViewHolder @@ -94,6 +97,7 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { bgExecutor, uiExecutor, activityStarter, + broadcastSender, keyguardStateController, taskViewFactory, metricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt index 87b9172dcefc..0166fa25d526 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt @@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastSender import com.android.wm.shell.TaskView import org.junit.Before import org.junit.Test @@ -39,6 +40,8 @@ class DetailDialogTest : SysuiTestCase() { @Mock private lateinit var taskView: TaskView @Mock + private lateinit var broadcastSender: BroadcastSender + @Mock private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent @@ -63,6 +66,7 @@ class DetailDialogTest : SysuiTestCase() { private fun createDialog(pendingIntent: PendingIntent): DetailDialog { return DetailDialog( mContext, + broadcastSender, taskView, pendingIntent, controlViewHolder diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index ff9e13a48bec..dcbdea0ac5a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -16,7 +16,6 @@ package com.android.systemui.dreams; -import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -30,7 +29,6 @@ import android.view.ViewTreeObserver; import androidx.test.filters.SmallTest; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.complication.ComplicationHostViewController; @@ -44,7 +42,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { - private static final int DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT = 100; private static final int MAX_BURN_IN_OFFSET = 20; private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10; private static final long MILLIS_UNTIL_FULL_JITTER = 240 * 1000; @@ -76,9 +73,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mResources.getDimensionPixelSize( - R.dimen.dream_overlay_notifications_drag_area_height)).thenReturn( - DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT); when(mDreamOverlayContainerView.getResources()).thenReturn(mResources); when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver); @@ -100,13 +94,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { } @Test - public void testSetsDreamOverlayNotificationsDragAreaHeight() { - assertEquals( - mController.getDreamOverlayNotificationsDragAreaHeight(), - DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT); - } - - @Test public void testBurnInProtectionStartsWhenContentViewAttached() { mController.onViewAttached(); verify(mHandler).postDelayed(any(Runnable.class), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index 35fda1392512..3657192daede 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -19,6 +19,7 @@ package com.android.systemui.dreams; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -173,19 +174,19 @@ public class DreamOverlayServiceTest extends SysuiTestCase { } @Test - public void testShouldShowComplicationsTrueByDefault() { + public void testShouldShowComplicationsFalseByDefault() { mService.onBind(new Intent()); - assertThat(mService.shouldShowComplications()).isTrue(); + assertThat(mService.shouldShowComplications()).isFalse(); } @Test public void testShouldShowComplicationsSetByIntentExtra() { final Intent intent = new Intent(); - intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, false); + intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, true); mService.onBind(intent); - assertThat(mService.shouldShowComplications()).isFalse(); + assertThat(mService.shouldShowComplications()).isTrue(); } @Test @@ -222,4 +223,25 @@ public class DreamOverlayServiceTest extends SysuiTestCase { verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED); verify(mStateController).setOverlayActive(false); } + + @Test + public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception { + when(mDreamOverlayContainerView.getParent()) + .thenReturn(mDreamOverlayContainerViewParent) + .thenReturn(null); + + final IBinder proxy = mService.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback); + + // Destroy the service. + mService.onDestroy(); + + // Run executor tasks. + mMainExecutor.runAllReady(); + + verify(mWindowManager, never()).addView(any(), any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 49da4bd5a825..3ce9889571f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -158,6 +158,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { public void testComplicationFilteringWhenShouldShowComplications() { final DreamOverlayStateController stateController = new DreamOverlayStateController(mExecutor); + stateController.setShouldShowComplications(true); final Complication alwaysAvailableComplication = Mockito.mock(Complication.class); final Complication weatherComplication = Mockito.mock(Complication.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java index 3b17a8071cb2..ed1cf69ad8c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java @@ -15,26 +15,31 @@ */ package com.android.systemui.dreams; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.smartspace.SmartspaceTarget; import android.content.Context; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; +import com.android.systemui.dreams.smartspace.DreamsSmartspaceController; +import com.android.systemui.plugins.BcSmartspaceDataPlugin; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.Arrays; + @SmallTest @RunWith(AndroidTestingRunner.class) public class SmartSpaceComplicationTest extends SysuiTestCase { @@ -42,7 +47,7 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { private Context mContext; @Mock - private LockscreenSmartspaceController mSmartspaceController; + private DreamsSmartspaceController mSmartspaceController; @Mock private DreamOverlayStateController mDreamOverlayStateController; @@ -60,7 +65,6 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { */ @Test public void testAvailability() { - when(mSmartspaceController.isEnabled()).thenReturn(false); final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant( mContext, @@ -68,10 +72,22 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { mComplication, mSmartspaceController); registrant.start(); - verify(mDreamOverlayStateController, never()).addComplication(any()); + verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication)); - when(mSmartspaceController.isEnabled()).thenReturn(true); - registrant.start(); + + final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture()); + + when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true); + dreamCallbackCaptor.getValue().onStateChanged(); + + final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor = + ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class); + verify(mSmartspaceController).addListener(listenerCaptor.capture()); + + final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class); + listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target)); verify(mDreamOverlayStateController).addComplication(eq(mComplication)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 6fead9efa767..f00fbe6ac4d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -341,6 +341,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { public void testCreateActionItems_lockdownEnabled_doesShowLockdown() { mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency(); doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); String[] actions = { @@ -365,8 +366,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { public void testCreateActionItems_lockdownDisabled_doesNotShowLockdown() { mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayEmergency(); // make sure lockdown action will NOT be shown doReturn(false).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); String[] actions = { GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, // lockdown action not allowed @@ -386,6 +389,32 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { } @Test + public void testCreateActionItems_emergencyDisabled_doesNotShowEmergency() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + // make sure emergency action will NOT be shown + doReturn(false).when(mGlobalActionsDialogLite).shouldDisplayEmergency(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + String[] actions = { + // emergency action not allowed + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + mGlobalActionsDialogLite.createActionItems(); + + assertItemsOfType(mGlobalActionsDialogLite.mItems, + GlobalActionsDialogLite.LockDownAction.class, + GlobalActionsDialogLite.ShutDownAction.class, + GlobalActionsDialogLite.RestartAction.class); + assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty(); + assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty(); + } + + @Test public void testShouldLogLockdownPress() { GlobalActionsDialogLite.LockDownAction lockDownAction = mGlobalActionsDialogLite.new LockDownAction(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 708fc915410c..cb68d81287df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -40,6 +40,7 @@ import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.dialog.MediaOutputDialogFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -59,6 +60,7 @@ import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever @@ -85,6 +87,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var bgExecutor: FakeExecutor @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var holder: PlayerViewHolder @Mock private lateinit var sessionHolder: PlayerSessionViewHolder @@ -119,8 +122,6 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var actionPlayPause: ImageButton private lateinit var actionNext: ImageButton private lateinit var actionPrev: ImageButton - private lateinit var actionStart: ImageButton - private lateinit var actionEnd: ImageButton @Mock private lateinit var longPressText: TextView @Mock private lateinit var handler: Handler private lateinit var settings: View @@ -144,8 +145,8 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mediaViewController.expandedLayout).thenReturn(expandedSet) whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet) - player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, - seekBarViewModel, Lazy { mediaDataManager }, + player = MediaControlPanel(context, bgExecutor, activityStarter, broadcastSender, + mediaViewController, seekBarViewModel, Lazy { mediaDataManager }, mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock) whenever(seekBarViewModel.progress).thenReturn(seekBarData) @@ -168,6 +169,17 @@ public class MediaControlPanelTest : SysuiTestCase() { cancelText = TextView(context) dismiss = FrameLayout(context) dismissText = TextView(context) + + action0 = ImageButton(context).also { it.setId(R.id.action0) } + action1 = ImageButton(context).also { it.setId(R.id.action1) } + action2 = ImageButton(context).also { it.setId(R.id.action2) } + action3 = ImageButton(context).also { it.setId(R.id.action3) } + action4 = ImageButton(context).also { it.setId(R.id.action4) } + + actionPlayPause = ImageButton(context).also { it.setId(R.id.actionPlayPause) } + actionPrev = ImageButton(context).also { it.setId(R.id.actionPrev) } + actionNext = ImageButton(context).also { it.setId(R.id.actionNext) } + initPlayerHolderMocks() initSessionHolderMocks() @@ -208,90 +220,64 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false) } - /** Mock view holder for the notification player */ - private fun initPlayerHolderMocks() { - whenever(holder.player).thenReturn(view) - whenever(holder.appIcon).thenReturn(appIcon) - whenever(holder.albumView).thenReturn(albumView) - whenever(holder.titleText).thenReturn(titleText) - whenever(holder.artistText).thenReturn(artistText) + /** + * Initialize elements common to both view holders + */ + private fun initMediaViewHolderMocks(viewHolder: MediaViewHolder) { + whenever(viewHolder.player).thenReturn(view) + whenever(viewHolder.appIcon).thenReturn(appIcon) + whenever(viewHolder.albumView).thenReturn(albumView) + whenever(viewHolder.titleText).thenReturn(titleText) + whenever(viewHolder.artistText).thenReturn(artistText) whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) - whenever(holder.seamless).thenReturn(seamless) - whenever(holder.seamlessButton).thenReturn(seamlessButton) - whenever(holder.seamlessIcon).thenReturn(seamlessIcon) - whenever(holder.seamlessText).thenReturn(seamlessText) - whenever(holder.seekBar).thenReturn(seekBar) - whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView) - whenever(holder.totalTimeView).thenReturn(totalTimeView) + whenever(viewHolder.seamless).thenReturn(seamless) + whenever(viewHolder.seamlessButton).thenReturn(seamlessButton) + whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon) + whenever(viewHolder.seamlessText).thenReturn(seamlessText) + whenever(viewHolder.seekBar).thenReturn(seekBar) // Action buttons - action0 = ImageButton(context) - whenever(holder.action0).thenReturn(action0) - whenever(holder.getAction(R.id.action0)).thenReturn(action0) - action1 = ImageButton(context) - whenever(holder.action1).thenReturn(action1) - whenever(holder.getAction(R.id.action1)).thenReturn(action1) - action2 = ImageButton(context) - whenever(holder.action2).thenReturn(action2) - whenever(holder.getAction(R.id.action2)).thenReturn(action2) - action3 = ImageButton(context) - whenever(holder.action3).thenReturn(action3) - whenever(holder.getAction(R.id.action3)).thenReturn(action3) - action4 = ImageButton(context) - whenever(holder.action4).thenReturn(action4) - whenever(holder.getAction(R.id.action4)).thenReturn(action4) + whenever(viewHolder.action0).thenReturn(action0) + whenever(viewHolder.getAction(R.id.action0)).thenReturn(action0) + whenever(viewHolder.action1).thenReturn(action1) + whenever(viewHolder.getAction(R.id.action1)).thenReturn(action1) + whenever(viewHolder.action2).thenReturn(action2) + whenever(viewHolder.getAction(R.id.action2)).thenReturn(action2) + whenever(viewHolder.action3).thenReturn(action3) + whenever(viewHolder.getAction(R.id.action3)).thenReturn(action3) + whenever(viewHolder.action4).thenReturn(action4) + whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4) // Long press menu - whenever(holder.longPressText).thenReturn(longPressText) + whenever(viewHolder.longPressText).thenReturn(longPressText) whenever(longPressText.handler).thenReturn(handler) - whenever(holder.settings).thenReturn(settings) - whenever(holder.settingsText).thenReturn(settingsText) - whenever(holder.cancel).thenReturn(cancel) - whenever(holder.cancelText).thenReturn(cancelText) - whenever(holder.dismiss).thenReturn(dismiss) - whenever(holder.dismissText).thenReturn(dismissText) + whenever(viewHolder.settings).thenReturn(settings) + whenever(viewHolder.settingsText).thenReturn(settingsText) + whenever(viewHolder.cancel).thenReturn(cancel) + whenever(viewHolder.cancelText).thenReturn(cancelText) + whenever(viewHolder.dismiss).thenReturn(dismiss) + whenever(viewHolder.dismissText).thenReturn(dismissText) + } + + /** Mock view holder for the notification player */ + private fun initPlayerHolderMocks() { + initMediaViewHolderMocks(holder) + + whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView) + whenever(holder.totalTimeView).thenReturn(totalTimeView) } /** Mock view holder for session player */ private fun initSessionHolderMocks() { - whenever(sessionHolder.player).thenReturn(view) - whenever(sessionHolder.albumView).thenReturn(albumView) - whenever(sessionHolder.appIcon).thenReturn(appIcon) - whenever(sessionHolder.titleText).thenReturn(titleText) - whenever(sessionHolder.artistText).thenReturn(artistText) - val seamlessBackground = mock(RippleDrawable::class.java) - whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) - whenever(sessionHolder.seamless).thenReturn(seamless) - whenever(sessionHolder.seamlessButton).thenReturn(seamlessButton) - whenever(sessionHolder.seamlessIcon).thenReturn(seamlessIcon) - whenever(sessionHolder.seamlessText).thenReturn(seamlessText) - whenever(sessionHolder.seekBar).thenReturn(seekBar) + initMediaViewHolderMocks(sessionHolder) - // Action buttons - actionPlayPause = ImageButton(context) + // Semantic action buttons whenever(sessionHolder.actionPlayPause).thenReturn(actionPlayPause) whenever(sessionHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause) - actionNext = ImageButton(context) whenever(sessionHolder.actionNext).thenReturn(actionNext) whenever(sessionHolder.getAction(R.id.actionNext)).thenReturn(actionNext) - actionPrev = ImageButton(context) whenever(sessionHolder.actionPrev).thenReturn(actionPrev) whenever(sessionHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev) - actionStart = ImageButton(context) - whenever(sessionHolder.actionStart).thenReturn(actionStart) - whenever(sessionHolder.getAction(R.id.actionStart)).thenReturn(actionStart) - actionEnd = ImageButton(context) - whenever(sessionHolder.actionEnd).thenReturn(actionEnd) - whenever(sessionHolder.getAction(R.id.actionEnd)).thenReturn(actionEnd) - - // Long press menu - whenever(sessionHolder.longPressText).thenReturn(longPressText) - whenever(sessionHolder.settings).thenReturn(settings) - whenever(sessionHolder.settingsText).thenReturn(settingsText) - whenever(sessionHolder.cancel).thenReturn(cancel) - whenever(sessionHolder.cancelText).thenReturn(cancelText) - whenever(sessionHolder.dismiss).thenReturn(dismiss) - whenever(sessionHolder.dismissText).thenReturn(dismissText) } @After @@ -316,8 +302,8 @@ public class MediaControlPanelTest : SysuiTestCase() { val semanticActions = MediaButton( playOrPause = MediaAction(icon, Runnable {}, "play"), nextOrCustom = MediaAction(icon, Runnable {}, "next"), - startCustom = MediaAction(icon, null, "custom 1"), - endCustom = MediaAction(icon, null, "custom 2") + custom0 = MediaAction(icon, null, "custom 0"), + custom1 = MediaAction(icon, null, "custom 1") ) val state = mediaData.copy(semanticActions = semanticActions) @@ -325,7 +311,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player.bindPlayer(state, PACKAGE) verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE) - assertThat(action0.contentDescription).isEqualTo("custom 1") + assertThat(action0.contentDescription).isEqualTo("custom 0") assertThat(action0.isEnabled()).isFalse() verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE) @@ -340,7 +326,7 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(action3.contentDescription).isEqualTo("next") verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE) - assertThat(action4.contentDescription).isEqualTo("custom 2") + assertThat(action4.contentDescription).isEqualTo("custom 1") assertThat(action4.isEnabled()).isFalse() } @@ -353,28 +339,96 @@ public class MediaControlPanelTest : SysuiTestCase() { val semanticActions = MediaButton( playOrPause = MediaAction(icon, Runnable {}, "play"), nextOrCustom = MediaAction(icon, Runnable {}, "next"), - startCustom = MediaAction(icon, null, "custom 1"), - endCustom = MediaAction(icon, null, "custom 2") + custom0 = MediaAction(icon, null, "custom 0"), + custom1 = MediaAction(icon, null, "custom 1") ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION) player.bindPlayer(state, PACKAGE) - assertThat(actionStart.contentDescription).isEqualTo("custom 1") - assertThat(actionStart.isEnabled()).isFalse() - assertThat(actionPrev.isEnabled()).isFalse() assertThat(actionPrev.drawable).isNull() + verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE) assertThat(actionPlayPause.isEnabled()).isTrue() assertThat(actionPlayPause.contentDescription).isEqualTo("play") + verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.VISIBLE) assertThat(actionNext.isEnabled()).isTrue() assertThat(actionNext.contentDescription).isEqualTo("next") + verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.VISIBLE) + + // Called twice since these IDs are used as generic buttons + assertThat(action0.contentDescription).isEqualTo("custom 0") + assertThat(action0.isEnabled()).isFalse() + verify(collapsedSet, times(2)).setVisibility(R.id.action0, ConstraintSet.GONE) + + assertThat(action1.contentDescription).isEqualTo("custom 1") + assertThat(action1.isEnabled()).isFalse() + verify(collapsedSet, times(2)).setVisibility(R.id.action1, ConstraintSet.GONE) + + // Verify generic buttons are hidden + verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.GONE) + } + + @Test + fun bindNotificationActionsNewLayout() { + whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true) + whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true) + + val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) + val actions = listOf( + MediaAction(icon, Runnable {}, "previous"), + MediaAction(icon, Runnable {}, "play"), + MediaAction(icon, null, "next"), + MediaAction(icon, null, "custom 0"), + MediaAction(icon, Runnable {}, "custom 1") + ) + val state = mediaData.copy(actions = actions, + actionsToShowInCompact = listOf(1, 2), + semanticActions = null) + + player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION) + player.bindPlayer(state, PACKAGE) + + // Verify semantic actions are hidden + verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE) + + verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE) + verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE) + + // Generic actions all enabled + assertThat(action0.contentDescription).isEqualTo("previous") + assertThat(action0.isEnabled()).isTrue() + verify(collapsedSet).setVisibility(R.id.action0, ConstraintSet.GONE) + + assertThat(action1.contentDescription).isEqualTo("play") + assertThat(action1.isEnabled()).isTrue() + verify(collapsedSet).setVisibility(R.id.action1, ConstraintSet.VISIBLE) + + assertThat(action2.contentDescription).isEqualTo("next") + assertThat(action2.isEnabled()).isFalse() + verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE) + + assertThat(action3.contentDescription).isEqualTo("custom 0") + assertThat(action3.isEnabled()).isFalse() + verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE) - assertThat(actionEnd.contentDescription).isEqualTo("custom 2") - assertThat(actionEnd.isEnabled()).isFalse() + assertThat(action4.contentDescription).isEqualTo("custom 1") + assertThat(action4.isEnabled()).isTrue() + verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt index 6b203bcf6828..82a48efafa3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -23,6 +23,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -65,6 +66,8 @@ class MediaDataFilterTest : SysuiTestCase() { @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher @Mock + private lateinit var broadcastSender: BroadcastSender + @Mock private lateinit var mediaResumeListener: MediaResumeListener @Mock private lateinit var mediaDataManager: MediaDataManager @@ -87,7 +90,7 @@ class MediaDataFilterTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) MediaPlayerData.clear() - mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, + mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, broadcastSender, lockscreenUserManager, executor, clock) mediaDataFilter.mediaDataManager = mediaDataManager mediaDataFilter.addListener(listener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 021f70e05b32..925ae30e8773 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -659,11 +659,11 @@ class MediaDataManagerTest : SysuiTestCase() { actions.nextOrCustom!!.action!!.run() verify(transportControls).skipToNext() - assertThat(actions.startCustom).isNotNull() - assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0]) + assertThat(actions.custom0).isNotNull() + assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0]) - assertThat(actions.endCustom).isNotNull() - assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1]) + assertThat(actions.custom1).isNotNull() + assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1]) } @Test @@ -697,11 +697,11 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(actions.nextOrCustom).isNotNull() assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1]) - assertThat(actions.startCustom).isNotNull() - assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2]) + assertThat(actions.custom0).isNotNull() + assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[2]) - assertThat(actions.endCustom).isNotNull() - assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3]) + assertThat(actions.custom1).isNotNull() + assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3]) } @Test @@ -737,10 +737,10 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(actions.prevOrCustom).isNull() assertThat(actions.nextOrCustom).isNull() - assertThat(actions.startCustom).isNotNull() - assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0]) + assertThat(actions.custom0).isNotNull() + assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0]) - assertThat(actions.endCustom).isNotNull() - assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1]) + assertThat(actions.custom1).isNotNull() + assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1]) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt index 3c2392a30731..7ac15125ea7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt @@ -26,29 +26,33 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase 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.mockito.Mock -import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -public class SeekBarObserverTest : SysuiTestCase() { +class SeekBarObserverTest : SysuiTestCase() { private val disabledHeight = 1 private val enabledHeight = 2 private lateinit var observer: SeekBarObserver @Mock private lateinit var mockHolder: PlayerViewHolder + @Mock private lateinit var mockSquigglyProgress: SquigglyProgress private lateinit var seekBarView: SeekBar private lateinit var elapsedTimeView: TextView private lateinit var totalTimeView: TextView + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() + @Before fun setUp() { - mockHolder = mock(PlayerViewHolder::class.java) context.orCreateTestableResources .addOverride(R.dimen.qs_media_enabled_seekbar_height, enabledHeight) @@ -56,6 +60,7 @@ public class SeekBarObserverTest : SysuiTestCase() { .addOverride(R.dimen.qs_media_disabled_seekbar_height, disabledHeight) seekBarView = SeekBar(context) + seekBarView.progressDrawable = mockSquigglyProgress elapsedTimeView = TextView(context) totalTimeView = TextView(context) whenever(mockHolder.seekBar).thenReturn(seekBarView) @@ -69,7 +74,7 @@ public class SeekBarObserverTest : SysuiTestCase() { fun seekBarGone() { // WHEN seek bar is disabled val isEnabled = false - val data = SeekBarViewModel.Progress(isEnabled, false, null, 0) + val data = SeekBarViewModel.Progress(isEnabled, false, false, null, 0) observer.onChanged(data) // THEN seek bar shows just a thin line with no text assertThat(seekBarView.isEnabled()).isFalse() @@ -84,7 +89,7 @@ public class SeekBarObserverTest : SysuiTestCase() { fun seekBarVisible() { // WHEN seek bar is enabled val isEnabled = true - val data = SeekBarViewModel.Progress(isEnabled, true, 3000, 12000) + val data = SeekBarViewModel.Progress(isEnabled, true, false, 3000, 12000) observer.onChanged(data) // THEN seek bar is visible and thick assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE) @@ -96,7 +101,7 @@ public class SeekBarObserverTest : SysuiTestCase() { @Test fun seekBarProgress() { // WHEN part of the track has been played - val data = SeekBarViewModel.Progress(true, true, 3000, 120000) + val data = SeekBarViewModel.Progress(true, true, true, 3000, 120000) observer.onChanged(data) // THEN seek bar shows the progress assertThat(seekBarView.progress).isEqualTo(3000) @@ -112,7 +117,7 @@ public class SeekBarObserverTest : SysuiTestCase() { fun seekBarDisabledWhenSeekNotAvailable() { // WHEN seek is not available val isSeekAvailable = false - val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000) + val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, 3000, 120000) observer.onChanged(data) // THEN seek bar is not enabled assertThat(seekBarView.isEnabled()).isFalse() @@ -122,9 +127,29 @@ public class SeekBarObserverTest : SysuiTestCase() { fun seekBarEnabledWhenSeekNotAvailable() { // WHEN seek is available val isSeekAvailable = true - val data = SeekBarViewModel.Progress(true, isSeekAvailable, 3000, 120000) + val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, 3000, 120000) observer.onChanged(data) // THEN seek bar is not enabled assertThat(seekBarView.isEnabled()).isTrue() } + + @Test + fun seekBarPlaying() { + // WHEN playing + val isPlaying = true + val data = SeekBarViewModel.Progress(true, true, isPlaying, 3000, 120000) + observer.onChanged(data) + // THEN progress drawable is animating + verify(mockSquigglyProgress).animate = true + } + + @Test + fun seekBarNotPlaying() { + // WHEN not playing + val isPlaying = false + val data = SeekBarViewModel.Progress(true, true, isPlaying, 3000, 120000) + observer.onChanged(data) + // THEN progress drawable is not animating + verify(mockSquigglyProgress).animate = false + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt new file mode 100644 index 000000000000..0787fd65eae1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt @@ -0,0 +1,118 @@ +package com.android.systemui.media + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.LightingColorFilter +import android.graphics.Paint +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +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.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class SquigglyProgressTest : SysuiTestCase() { + + private val colorFilter = LightingColorFilter(Color.RED, Color.BLUE) + private val strokeWidth = 5f + private val alpha = 128 + private val tint = Color.GREEN + + lateinit var squigglyProgress: SquigglyProgress + @Mock lateinit var canvas: Canvas + @Captor lateinit var wavePaintCaptor: ArgumentCaptor<Paint> + @Captor lateinit var linePaintCaptor: ArgumentCaptor<Paint> + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() + + @Before + fun setup() { + squigglyProgress = SquigglyProgress() + squigglyProgress.waveLength = 30f + squigglyProgress.lineAmplitude = 10f + squigglyProgress.phaseSpeed = 8f + squigglyProgress.strokeWidth = strokeWidth + squigglyProgress.bounds = Rect(0, 0, 300, 30) + } + + @Test + fun testDrawPathAndLine() { + squigglyProgress.draw(canvas) + + verify(canvas).drawPath(any(), wavePaintCaptor.capture()) + verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), + linePaintCaptor.capture()) + } + + @Test + fun testOnLevelChanged() { + assertThat(squigglyProgress.setLevel(5)).isFalse() + squigglyProgress.animate = true + assertThat(squigglyProgress.setLevel(4)).isTrue() + } + + @Test + fun testStrokeWidth() { + squigglyProgress.draw(canvas) + + verify(canvas).drawPath(any(), wavePaintCaptor.capture()) + verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), + linePaintCaptor.capture()) + + assertThat(wavePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth) + assertThat(linePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth) + } + + @Test + fun testAlpha() { + squigglyProgress.alpha = alpha + squigglyProgress.draw(canvas) + + verify(canvas).drawPath(any(), wavePaintCaptor.capture()) + verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), + linePaintCaptor.capture()) + + assertThat(squigglyProgress.alpha).isEqualTo(alpha) + assertThat(wavePaintCaptor.value.alpha).isEqualTo(alpha) + assertThat(linePaintCaptor.value.alpha).isEqualTo((alpha / 255f * DISABLED_ALPHA).toInt()) + } + + @Test + fun testColorFilter() { + squigglyProgress.colorFilter = colorFilter + squigglyProgress.draw(canvas) + + verify(canvas).drawPath(any(), wavePaintCaptor.capture()) + verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), + linePaintCaptor.capture()) + + assertThat(wavePaintCaptor.value.colorFilter).isEqualTo(colorFilter) + assertThat(linePaintCaptor.value.colorFilter).isEqualTo(colorFilter) + } + + @Test + fun testTint() { + squigglyProgress.setTint(tint) + squigglyProgress.draw(canvas) + + verify(canvas).drawPath(any(), wavePaintCaptor.capture()) + verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), + linePaintCaptor.capture()) + + assertThat(wavePaintCaptor.value.color).isEqualTo(tint) + assertThat(linePaintCaptor.value.color).isEqualTo(tint) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 13e582196ffb..380fa6d50df7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -37,15 +37,14 @@ import android.widget.TextView; import androidx.core.graphics.drawable.IconCompat; import androidx.test.filters.SmallTest; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.phone.ShadeController; import org.junit.Before; import org.junit.Test; @@ -64,11 +63,10 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class); private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class); - private ShadeController mShadeController = mock(ShadeController.class); private ActivityStarter mStarter = mock(ActivityStarter.class); + private BroadcastSender mBroadcastSender = mock(BroadcastSender.class); private NotificationEntryManager mNotificationEntryManager = mock(NotificationEntryManager.class); - private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); @@ -77,17 +75,16 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private MediaOutputController mMediaOutputController; private int mHeaderIconRes; private IconCompat mIconCompat; - private Drawable mAppSourceDrawable; private CharSequence mHeaderTitle; private CharSequence mHeaderSubtitle; @Before public void setUp() { - mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, - mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, + mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotificationEntryManager, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager)); - mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, + mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); } @@ -178,15 +175,16 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog { - MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) { - super(context, mediaOutputController); + MediaOutputBaseDialogImpl(Context context, BroadcastSender broadcastSender, + MediaOutputController mediaOutputController) { + super(context, broadcastSender, mediaOutputController); mAdapter = mMediaOutputBaseAdapter; } @Override Drawable getAppSourceIcon() { - return mAppSourceDrawable; + return null; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 6230700a6a2e..d2dae745bcdc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -45,7 +45,6 @@ import android.text.TextUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.test.filters.SmallTest; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.LocalMediaManager; @@ -57,7 +56,6 @@ import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; -import com.android.systemui.statusbar.phone.ShadeController; import com.google.common.collect.ImmutableList; @@ -96,10 +94,8 @@ public class MediaOutputControllerTest extends SysuiTestCase { private NearbyDevice mNearbyDevice2 = mock(NearbyDevice.class); private MediaMetadata mMediaMetadata = mock(MediaMetadata.class); private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class); - private ShadeController mShadeController = mock(ShadeController.class); private ActivityStarter mStarter = mock(ActivityStarter.class); private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class); - private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); @@ -124,9 +120,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn( mCachedBluetoothDeviceManager); - mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false, - mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, + mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager)); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; @@ -176,9 +172,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void start_withoutPackageName_verifyMediaControllerInit() { - mMediaOutputController = new MediaOutputController(mSpyContext, null, false, - mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, + mMediaOutputController = new MediaOutputController(mSpyContext, null, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager)); mMediaOutputController.start(mCb); @@ -205,9 +201,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void stop_withoutPackageName_verifyMediaControllerDeinit() { - mMediaOutputController = new MediaOutputController(mSpyContext, null, false, - mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, + mMediaOutputController = new MediaOutputController(mSpyContext, null, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager)); mMediaOutputController.start(mCb); @@ -510,9 +506,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void getNotificationLargeIcon_withoutPackageName_returnsNull() { - mMediaOutputController = new MediaOutputController(mSpyContext, null, false, - mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, + mMediaOutputController = new MediaOutputController(mSpyContext, null, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager)); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index cb52e7c20464..db56f875dd5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -37,10 +37,10 @@ import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.phone.ShadeController; import org.junit.After; import org.junit.Before; @@ -61,8 +61,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { // Mock private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class); - private final ShadeController mShadeController = mock(ShadeController.class); private final ActivityStarter mStarter = mock(ActivityStarter.class); + private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class); private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class); private final MediaDevice mMediaDevice = mock(MediaDevice.class); private final NotificationEntryManager mNotificationEntryManager = @@ -78,12 +78,12 @@ public class MediaOutputDialogTest extends SysuiTestCase { @Before public void setUp() { - mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, - mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, + mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotificationEntryManager, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager)); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputDialog = new MediaOutputDialog(mContext, false, + mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, mMediaOutputController, mUiEventLogger); mMediaOutputDialog.show(); @@ -129,7 +129,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { // Check the visibility metric logging by creating a new MediaOutput dialog, // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, + MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, mMediaOutputController, mUiEventLogger); testDialog.show(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java index f186f57fd0e5..0cdde0775b2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java @@ -28,17 +28,16 @@ import android.view.View; import androidx.test.filters.SmallTest; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.phone.ShadeController; import org.junit.After; import org.junit.Before; @@ -59,14 +58,13 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { // Mock private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class); - private ShadeController mShadeController = mock(ShadeController.class); private ActivityStarter mStarter = mock(ActivityStarter.class); + private BroadcastSender mBroadcastSender = mock(BroadcastSender.class); private LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class); private MediaDevice mMediaDevice = mock(MediaDevice.class); private MediaDevice mMediaDevice1 = mock(MediaDevice.class); private NotificationEntryManager mNotificationEntryManager = mock(NotificationEntryManager.class); - private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); @@ -77,12 +75,12 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { @Before public void setUp() { - mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, - mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, + mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, + mMediaSessionManager, mLocalBluetoothManager, mStarter, + mNotificationEntryManager, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager)); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, + mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, mBroadcastSender, mMediaOutputController); mMediaOutputGroupDialog.show(); when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt index f99efe25c6eb..ccce5778c150 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt @@ -20,7 +20,7 @@ import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import android.media.MediaRoute2Info +import android.os.PowerManager import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -30,7 +30,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor @@ -71,6 +70,8 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { private lateinit var viewUtil: ViewUtil @Mock private lateinit var tapGestureDetector: TapGestureDetector + @Mock + private lateinit var powerManager: PowerManager @Before fun setUp() { @@ -88,16 +89,17 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { fakeExecutor = FakeExecutor(fakeClock) controllerCommon = TestControllerCommon( - context, logger, windowManager, viewUtil, fakeExecutor, tapGestureDetector + context, logger, windowManager, viewUtil, fakeExecutor, tapGestureDetector, powerManager ) } @Test - fun displayChip_chipAddedAndGestureDetectionStarted() { + fun displayChip_chipAddedAndGestureDetectionStartedAndScreenOn() { controllerCommon.displayChip(getState()) verify(windowManager).addView(any(), any()) verify(tapGestureDetector).addOnGestureDetectedCallback(any(), any()) + verify(powerManager).wakeUp(any(), any(), any()) } @Test @@ -281,6 +283,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { viewUtil: ViewUtil, @Main mainExecutor: DelayableExecutor, tapGestureDetector: TapGestureDetector, + powerManager: PowerManager ) : MediaTttChipControllerCommon<ChipInfo>( context, logger, @@ -288,6 +291,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { viewUtil, mainExecutor, tapGestureDetector, + powerManager, R.layout.media_ttt_chip ) { override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 36b5761ca690..355d3fe4b8d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -22,6 +22,7 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.os.Handler +import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -64,6 +65,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTttLogger @Mock + private lateinit var powerManager: PowerManager + @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var viewUtil: ViewUtil @@ -97,6 +100,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { viewUtil, FakeExecutor(FakeSystemClock()), TapGestureDetector(context), + powerManager, Handler.getMain(), receiverUiEventLogger, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 4ac7709071c8..ef5315428a60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.MediaRoute2Info +import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -66,6 +67,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTttLogger @Mock + private lateinit var powerManager: PowerManager + @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var viewUtil: ViewUtil @@ -103,6 +106,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { viewUtil, fakeExecutor, TapGestureDetector(context), + powerManager, senderUiEventLogger ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index afb63ab0ebcf..a156820ad141 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -37,6 +37,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.util.NotificationChannels; @@ -59,7 +60,9 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { // Test Instance. mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager); ActivityStarter starter = mDependency.injectMockDependency(ActivityStarter.class); - mPowerNotificationWarnings = new PowerNotificationWarnings(mContext, starter); + BroadcastSender broadcastSender = mDependency.injectMockDependency(BroadcastSender.class); + mPowerNotificationWarnings = new PowerNotificationWarnings(mContext, starter, + broadcastSender); BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1, BatteryManager.BATTERY_HEALTH_GOOD, 5, 15); mPowerNotificationWarnings.updateSnapshot(snapshot); diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt new file mode 100644 index 000000000000..57803e874f93 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace + +import android.app.smartspace.SmartspaceManager +import android.app.smartspace.SmartspaceSession +import android.app.smartspace.SmartspaceTarget +import android.content.Context +import android.graphics.drawable.Drawable +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dreams.smartspace.DreamsSmartspaceController +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.smartspace.dagger.SmartspaceViewComponent +import com.android.systemui.util.concurrency.Execution +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.Optional +import java.util.concurrent.Executor + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class DreamSmartspaceControllerTest : SysuiTestCase() { + @Mock + private lateinit var smartspaceManager: SmartspaceManager + + @Mock + private lateinit var execution: Execution + + @Mock + private lateinit var uiExecutor: Executor + + @Mock + private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory + + @Mock + private lateinit var viewComponent: SmartspaceViewComponent + + @Mock + private lateinit var targetFilter: SmartspaceTargetFilter + + @Mock + private lateinit var plugin: BcSmartspaceDataPlugin + + @Mock + private lateinit var precondition: SmartspacePrecondition + + @Mock + private lateinit var smartspaceView: BcSmartspaceDataPlugin.SmartspaceView + + @Mock + private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener + + @Mock + private lateinit var session: SmartspaceSession + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(viewComponentFactory.create(any(), eq(plugin), any())) + .thenReturn(viewComponent) + `when`(viewComponent.getView()).thenReturn(smartspaceView) + `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) + } + + /** + * Ensures smartspace session begins on a listener only flow. + */ + @Test + fun testConnectOnListen() { + val controller = DreamsSmartspaceController(context, + smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition, + Optional.of(targetFilter), Optional.of(plugin)) + + `when`(precondition.conditionsMet()).thenReturn(true) + controller.addListener(listener) + + verify(smartspaceManager).createSmartspaceSession(any()) + + var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> { + verify(session).addOnTargetsAvailableListener(any(), capture()) + } + + `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true) + + var target = Mockito.mock(SmartspaceTarget::class.java) + targetListener.onTargetsAvailable(listOf(target)) + + var targets = withArgCaptor<List<SmartspaceTarget>> { + verify(plugin).onTargetsAvailable(capture()) + } + + assertThat(targets.contains(target)).isTrue() + + controller.removeListener(listener) + + verify(session).close() + } + + /** + * A class which implements SmartspaceView and extends View. This is mocked to provide the right + * object inheritance and interface implementation used in DreamSmartspaceController + */ + private class TestView(context: Context?) : View(context), + BcSmartspaceDataPlugin.SmartspaceView { + override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {} + + override fun setPrimaryTextColor(color: Int) {} + + override fun setDozeAmount(amount: Float) {} + + override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} + + override fun setFalsingManager(falsingManager: FalsingManager?) {} + + override fun setDnd(image: Drawable?, description: String?) {} + + override fun setNextAlarm(image: Drawable?, description: String?) {} + + override fun setMediaTarget(target: SmartspaceTarget?) {} + + override fun getSelectedPage(): Int { return 0; } + } + + /** + * Ensures session begins when a view is attached. + */ + @Test + fun testConnectOnViewCreate() { + val controller = DreamsSmartspaceController(context, + smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition, + Optional.of(targetFilter), + Optional.of(plugin)) + + `when`(precondition.conditionsMet()).thenReturn(true) + controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java)) + + var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> { + verify(viewComponentFactory).create(any(), eq(plugin), capture()) + } + + var mockView = Mockito.mock(TestView::class.java) + `when`(precondition.conditionsMet()).thenReturn(true) + stateChangeListener.onViewAttachedToWindow(mockView) + + verify(smartspaceManager).createSmartspaceSession(any()) + + stateChangeListener.onViewDetachedFromWindow(mockView) + + verify(session).close() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt new file mode 100644 index 000000000000..d29e9a66a331 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.smartspace + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.smartspace.preconditions.LockscreenPrecondition +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.util.concurrency.Execution +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class LockscreenPreconditionTest : SysuiTestCase() { + @Mock + private lateinit var featureFlags: FeatureFlags + + @Mock + private lateinit var deviceProvisionedController: DeviceProvisionedController + + @Mock + private lateinit var execution: Execution + + @Mock + private lateinit var listener: SmartspacePrecondition.Listener + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + /** + * Ensures fully enabled state is published. + */ + @Test + fun testFullyEnabled() { + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE)) + .thenReturn(true) + val precondition = LockscreenPrecondition(featureFlags, deviceProvisionedController, + execution) + precondition.addListener(listener) + + `verify`(listener).onCriteriaChanged() + assertThat(precondition.conditionsMet()).isTrue() + } + + /** + * Ensures fully enabled state is published. + */ + @Test + fun testProvisioning() { + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) + `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE)) + .thenReturn(true) + val precondition = + LockscreenPrecondition(featureFlags, deviceProvisionedController, execution) + precondition.addListener(listener) + + verify(listener).onCriteriaChanged() + assertThat(precondition.conditionsMet()).isFalse() + + var argumentCaptor = ArgumentCaptor.forClass(DeviceProvisionedController + .DeviceProvisionedListener::class.java) + verify(deviceProvisionedController).addCallback(argumentCaptor.capture()) + + Mockito.clearInvocations(listener) + + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + argumentCaptor.value.onDeviceProvisionedChanged() + verify(listener).onCriteriaChanged() + assertThat(precondition.conditionsMet()).isTrue() + } + + /** + * Makes sure user setup changes are propagated. + */ + @Test + fun testUserSetup() { + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE)) + .thenReturn(true) + val precondition = + LockscreenPrecondition(featureFlags, deviceProvisionedController, execution) + precondition.addListener(listener) + + verify(listener).onCriteriaChanged() + assertThat(precondition.conditionsMet()).isFalse() + + var argumentCaptor = ArgumentCaptor.forClass(DeviceProvisionedController + .DeviceProvisionedListener::class.java) + verify(deviceProvisionedController).addCallback(argumentCaptor.capture()) + + Mockito.clearInvocations(listener) + + `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) + argumentCaptor.value.onUserSetupChanged() + verify(listener).onCriteriaChanged() + assertThat(precondition.conditionsMet()).isTrue() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt new file mode 100644 index 000000000000..185a8384dd75 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenTargetFilterTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.smartspace + +import android.app.smartspace.SmartspaceTarget +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.UserTracker +import com.android.systemui.smartspace.filters.LockscreenTargetFilter +import com.android.systemui.util.concurrency.Execution +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.settings.SecureSettings +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.atLeast +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.concurrent.Executor + +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class LockscreenTargetFilterTest : SysuiTestCase() { + @Mock + private lateinit var secureSettings: SecureSettings + + @Mock + private lateinit var userTracker: UserTracker + + @Mock + private lateinit var execution: Execution + + @Mock + private lateinit var handler: Handler + + @Mock + private lateinit var contentResolver: ContentResolver + + @Mock + private lateinit var uiExecution: Executor + + @Mock + private lateinit var userHandle: UserHandle + + @Mock + private lateinit var listener: SmartspaceTargetFilter.Listener + + @Mock + private lateinit var lockScreenAllowPrivateNotificationsUri: Uri + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(userTracker.userHandle).thenReturn(userHandle) + `when`(secureSettings + .getUriFor(eq(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS))) + .thenReturn(lockScreenAllowPrivateNotificationsUri) + } + + /** + * Ensures {@link Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS} is + * tracked. + */ + @Test + fun testLockscreenAllowPrivateNotifications() { + var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS + `when`(secureSettings + .getIntForUser(eq(setting) ?: setting, anyInt(), anyInt())) + .thenReturn(0) + var filter = LockscreenTargetFilter(secureSettings, userTracker, execution, handler, + contentResolver, uiExecution) + + filter.addListener(listener) + var smartspaceTarget = mock(SmartspaceTarget::class.java) + `when`(smartspaceTarget.userHandle).thenReturn(userHandle) + `when`(smartspaceTarget.isSensitive).thenReturn(true) + assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isFalse() + + var settingCaptor = ArgumentCaptor.forClass(ContentObserver::class.java) + + verify(contentResolver).registerContentObserver(eq(lockScreenAllowPrivateNotificationsUri), + anyBoolean(), settingCaptor.capture(), anyInt()) + + `when`(secureSettings + .getIntForUser(eq(setting) ?: setting, anyInt(), anyInt())) + .thenReturn(1) + + clearInvocations(listener) + settingCaptor.value.onChange(false, mock(Uri::class.java)) + verify(listener, atLeast(1)).onCriteriaChanged() + assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isTrue() + } + + /** + * Ensures user switches are tracked. + */ + @Test + fun testUserSwitchCallback() { + var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS + `when`(secureSettings + .getIntForUser(eq(setting) ?: setting, anyInt(), anyInt())) + .thenReturn(0) + var filter = LockscreenTargetFilter(secureSettings, userTracker, execution, handler, + contentResolver, uiExecution) + + filter.addListener(listener) + + var userTrackerCallback = withArgCaptor<UserTracker.Callback> { + verify(userTracker).addCallback(capture(), any()) + } + + clearInvocations(listener) + userTrackerCallback.onUserChanged(0, context) + + verify(listener).onCriteriaChanged() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 25a5b900f15a..067caa98102b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -87,6 +87,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { row = helper.createRow() context.getOrCreateTestableResources() .addOverride(R.bool.config_use_split_notification_shade, false) + context.getOrCreateTestableResources() + .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 100) transitionController = LockscreenShadeTransitionController( statusBarStateController = statusbarStateController, logger = logger, @@ -247,6 +249,17 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { } @Test + fun testDragDownAmount_depthDistanceIsZero_doesNotSetProgress() { + context.getOrCreateTestableResources() + .addOverride(R.dimen.lockscreen_shade_depth_controller_transition_distance, 0) + configurationController.notifyConfigurationChanged() + + transitionController.dragDownAmount = 10f + + verify(depthController, never()).transitionToFullShadeProgress + } + + @Test fun setDragDownAmount_setsValueOnMediaHierarchyManager() { transitionController.dragDownAmount = 10f diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java index 353647b65cc0..4d8949562e08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java @@ -80,8 +80,8 @@ public class NotificationUiAdjustmentTest extends SysuiTestCase { Notification.Action firstAction = createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent).build(); Notification.Action secondAction = - createActionBuilder("same", R.drawable.ic_account_circle, pendingIntent) - .build(); + createActionBuilder("same", com.android.settingslib.R.drawable.ic_account_circle, + pendingIntent).build(); assertThat(NotificationUiAdjustment.needReinflate( createUiAdjustmentFromSmartActions("first", Collections.singletonList(firstAction)), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java index 953a330d2e65..3810783d4f76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesTest.java @@ -846,6 +846,7 @@ public class CentralSurfacesTest extends SysuiTestCase { @Test public void testTransitionLaunch_noPreview_doesntGoUnlocked() { mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD); + when(mKeyguardStateController.isShowing()).thenReturn(true); mCentralSurfaces.showKeyguardImpl(); // Starting a pulse should change the scrim controller to the pulsing state @@ -868,6 +869,7 @@ public class CentralSurfacesTest extends SysuiTestCase { @Test public void testPulseWhileDozing_updatesScrimController() { mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD); + when(mKeyguardStateController.isShowing()).thenReturn(true); mCentralSurfaces.showKeyguardImpl(); // Starting a pulse should change the scrim controller to the pulsing state diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 6842295ec5d5..4bac08ea536b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -56,13 +56,11 @@ import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.view.ViewStub; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; -import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import androidx.test.filters.SmallTest; @@ -389,8 +387,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null); - mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame)); - mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller)); mNotificationContainerParent.addView(mKeyguardStatusView); mNotificationContainerParent.onFinishInflate(); when(mView.findViewById(R.id.notification_container_parent)) @@ -679,31 +675,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testAllChildrenOfNotificationContainer_haveIds() { - enableSplitShade(/* enabled= */ true); - mNotificationContainerParent.removeAllViews(); - mNotificationContainerParent.addView(newViewWithId(1)); - mNotificationContainerParent.addView(newViewWithId(View.NO_ID)); - - mNotificationPanelViewController.updateResources(); - - assertThat(mNotificationContainerParent.getChildAt(0).getId()).isEqualTo(1); - assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID); - } - - @Test - public void testSinglePaneShadeLayout_isAlignedToParent() { - enableSplitShade(/* enabled= */ false); - - mNotificationPanelViewController.updateResources(); - - assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) - .isEqualTo(ConstraintSet.PARENT_ID); - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) - .isEqualTo(ConstraintSet.PARENT_ID); - } - - @Test public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -752,46 +723,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testSplitShadeLayout_isAlignedToGuideline() { - enableSplitShade(/* enabled= */ true); - - mNotificationPanelViewController.updateResources(); - - assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) - .isEqualTo(R.id.qs_edge_guideline); - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) - .isEqualTo(R.id.qs_edge_guideline); - } - - @Test - public void testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { - enableSplitShade(/* enabled= */ true); - - mNotificationPanelViewController.updateResources(); - - assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(10); - assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0); - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin) - .isEqualTo(0); - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).endMargin) - .isEqualTo(10); - } - - @Test - public void testSinglePaneLayout_childrenHaveEqualMargins() { - enableSplitShade(/* enabled= */ false); - - mNotificationPanelViewController.updateResources(); - - assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(10); - assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(10); - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin) - .isEqualTo(10); - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).endMargin) - .isEqualTo(10); - } - - @Test public void testCanCollapsePanelOnTouch_trueForKeyGuard() { mStatusBarStateController.setState(KEYGUARD); @@ -1016,17 +947,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } } - - private View newViewWithId(int id) { - View view = new View(mContext); - view.setId(id); - ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - // required as cloning ConstraintSet fails if view doesn't have layout params - view.setLayoutParams(layoutParams); - return view; - } - private ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(mNotificationContainerParent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt index 5fb4144bec3c..9e64f017d88e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt @@ -2,8 +2,14 @@ package com.android.systemui.statusbar.phone import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View +import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManagerPolicyConstants +import androidx.annotation.AnyRes +import androidx.annotation.IdRes +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -13,6 +19,7 @@ import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -58,8 +65,10 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener> @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>> + @Captor + lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet> - private lateinit var notificationsQSContainerController: NotificationsQSContainerController + private lateinit var controller: NotificationsQSContainerController private lateinit var navigationModeCallback: ModeChangedListener private lateinit var taskbarVisibilityCallback: OverviewProxyListener private lateinit var windowInsetsCallback: Consumer<WindowInsets> @@ -68,36 +77,40 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) mContext.ensureTestableResources() + whenever(notificationsQSContainer.context).thenReturn(mContext) whenever(notificationsQSContainer.resources).thenReturn(mContext.resources) - notificationsQSContainerController = NotificationsQSContainerController( + controller = NotificationsQSContainerController( notificationsQSContainer, navigationModeController, overviewProxyService, featureFlags ) - mContext.orCreateTestableResources - .addOverride(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) - - whenever(notificationsQSContainer.defaultNotificationsMarginBottom) - .thenReturn(NOTIFICATIONS_MARGIN) + overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) + overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN) + overrideResource(R.bool.config_use_split_notification_shade, false) whenever(navigationModeController.addListener(navigationModeCaptor.capture())) .thenReturn(GESTURES_NAVIGATION) doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture()) doNothing().`when`(notificationsQSContainer) .setInsetsChangedListener(windowInsetsCallbackCaptor.capture()) + doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture()) - notificationsQSContainerController.init() - notificationsQSContainerController.onViewAttached() + controller.init() + controller.onViewAttached() navigationModeCallback = navigationModeCaptor.value taskbarVisibilityCallback = taskbarVisibilityCaptor.value windowInsetsCallback = windowInsetsCallbackCaptor.value } + private fun overrideResource(@AnyRes id: Int, value: Any) { + mContext.orCreateTestableResources.addOverride(id, value) + } + @Test fun testTaskbarVisibleInSplitShade() { - notificationsQSContainerController.splitShadeEnabled = true + enableSplitShade() useNewFooter(false) given(taskbarVisible = true, @@ -115,7 +128,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarVisibleInSplitShade_newFooter() { - notificationsQSContainerController.splitShadeEnabled = true + enableSplitShade() useNewFooter(true) given(taskbarVisible = true, @@ -136,7 +149,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSplitShade() { // when taskbar is not visible, it means we're on the home screen - notificationsQSContainerController.splitShadeEnabled = true + enableSplitShade() useNewFooter(false) given(taskbarVisible = false, @@ -154,7 +167,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSplitShade_newFooter() { // when taskbar is not visible, it means we're on the home screen - notificationsQSContainerController.splitShadeEnabled = true + enableSplitShade() useNewFooter(true) given(taskbarVisible = false, @@ -173,7 +186,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSplitShadeWithCutout() { - notificationsQSContainerController.splitShadeEnabled = true + enableSplitShade() useNewFooter(false) given(taskbarVisible = false, @@ -190,7 +203,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() { - notificationsQSContainerController.splitShadeEnabled = true + enableSplitShade() useNewFooter(true) given(taskbarVisible = false, @@ -209,7 +222,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarVisibleInSinglePaneShade() { - notificationsQSContainerController.splitShadeEnabled = false + disableSplitShade() useNewFooter(false) given(taskbarVisible = true, @@ -225,7 +238,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarVisibleInSinglePaneShade_newFooter() { - notificationsQSContainerController.splitShadeEnabled = false + disableSplitShade() useNewFooter(true) given(taskbarVisible = true, @@ -243,7 +256,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSinglePaneShade() { - notificationsQSContainerController.splitShadeEnabled = false + disableSplitShade() useNewFooter(false) given(taskbarVisible = false, @@ -264,7 +277,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testTaskbarNotVisibleInSinglePaneShade_newFooter() { - notificationsQSContainerController.splitShadeEnabled = false + disableSplitShade() useNewFooter(true) given(taskbarVisible = false, @@ -285,8 +298,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testCustomizingInSinglePaneShade() { - notificationsQSContainerController.splitShadeEnabled = false - notificationsQSContainerController.setCustomizerShowing(true) + disableSplitShade() + controller.setCustomizerShowing(true) useNewFooter(false) // always sets spacings to 0 @@ -305,8 +318,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testCustomizingInSinglePaneShade_newFooter() { - notificationsQSContainerController.splitShadeEnabled = false - notificationsQSContainerController.setCustomizerShowing(true) + disableSplitShade() + controller.setCustomizerShowing(true) useNewFooter(true) // always sets spacings to 0 @@ -325,8 +338,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testDetailShowingInSinglePaneShade() { - notificationsQSContainerController.splitShadeEnabled = false - notificationsQSContainerController.setDetailShowing(true) + disableSplitShade() + controller.setDetailShowing(true) useNewFooter(false) // always sets spacings to 0 @@ -345,8 +358,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testDetailShowingInSinglePaneShade_newFooter() { - notificationsQSContainerController.splitShadeEnabled = false - notificationsQSContainerController.setDetailShowing(true) + disableSplitShade() + controller.setDetailShowing(true) useNewFooter(true) // always sets spacings to 0 @@ -365,8 +378,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testDetailShowingInSplitShade() { - notificationsQSContainerController.splitShadeEnabled = true - notificationsQSContainerController.setDetailShowing(true) + enableSplitShade() + controller.setDetailShowing(true) useNewFooter(false) given(taskbarVisible = false, @@ -383,8 +396,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testDetailShowingInSplitShade_newFooter() { - notificationsQSContainerController.splitShadeEnabled = true - notificationsQSContainerController.setDetailShowing(true) + enableSplitShade() + controller.setDetailShowing(true) useNewFooter(true) given(taskbarVisible = false, @@ -402,16 +415,86 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Test fun testNotificationsMarginBottomIsUpdated() { Mockito.clearInvocations(notificationsQSContainer) - notificationsQSContainerController.splitShadeEnabled = true + enableSplitShade() verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN) - whenever(notificationsQSContainer.defaultNotificationsMarginBottom).thenReturn(100) - notificationsQSContainerController.updateMargins() - notificationsQSContainerController.splitShadeEnabled = false - + overrideResource(R.dimen.notification_panel_margin_bottom, 100) + disableSplitShade() verify(notificationsQSContainer).setNotificationsMarginBottom(100) } + @Test + fun testSplitShadeLayout_isAlignedToGuideline() { + enableSplitShade() + controller.updateResources() + assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) + .isEqualTo(R.id.qs_edge_guideline) + assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) + .isEqualTo(R.id.qs_edge_guideline) + } + + @Test + fun testSinglePaneLayout_childrenHaveEqualMargins() { + disableSplitShade() + controller.updateResources() + val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin + val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin + val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin + val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin + assertThat(qsStartMargin == qsEndMargin && + notifStartMargin == notifEndMargin && + qsStartMargin == notifStartMargin + ).isTrue() + } + + @Test + fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { + enableSplitShade() + controller.updateResources() + assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0) + assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin) + .isEqualTo(0) + } + + @Test + fun testSinglePaneShadeLayout_isAlignedToParent() { + disableSplitShade() + controller.updateResources() + assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) + .isEqualTo(ConstraintSet.PARENT_ID) + assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) + .isEqualTo(ConstraintSet.PARENT_ID) + } + + @Test + fun testAllChildrenOfNotificationContainer_haveIds() { + // set dimen to 0 to avoid triggering updating bottom spacing + overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0) + val container = NotificationsQuickSettingsContainer(context, null) + container.removeAllViews() + container.addView(newViewWithId(1)) + container.addView(newViewWithId(View.NO_ID)) + val controller = NotificationsQSContainerController(container, navigationModeController, + overviewProxyService, featureFlags) + controller.updateResources() + + assertThat(container.getChildAt(0).id).isEqualTo(1) + assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID) + } + + private fun disableSplitShade() { + setSplitShadeEnabled(false) + } + + private fun enableSplitShade() { + setSplitShadeEnabled(true) + } + + private fun setSplitShadeEnabled(enabled: Boolean) { + overrideResource(R.bool.config_use_split_notification_shade, enabled) + controller.updateResources() + } + private fun given( taskbarVisible: Boolean, navigationMode: Int, @@ -458,4 +541,18 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { private fun useNewFooter(useNewFooter: Boolean) { whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter) } -}
\ No newline at end of file + + private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout { + return constraintSetCaptor.value.getConstraint(id).layout + } + + private fun newViewWithId(id: Int): View { + val view = View(mContext) + view.id = id + val layoutParams = ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + // required as cloning ConstraintSet fails if view doesn't have layout params + view.layoutParams = layoutParams + return view + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 10f4435d3f97..0b25467d20d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -1234,10 +1234,8 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.transitionTo(ScrimState.KEYGUARD); mScrimController.setUnocclusionAnimationRunning(true); - assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA, - /* expansion */ 0.0f); - assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA, - /* expansion */ 1.0f); + assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0f, /* expansion */ 0f); + assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0f, /* expansion */ 1.0f); // Verify normal behavior after mScrimController.setUnocclusionAnimationRunning(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index 91c347fc4685..1caacb89ba10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -83,6 +84,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Mock private lateinit var userManager: UserManager @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager @Mock private lateinit var secureSettings: SecureSettings @Mock private lateinit var falsingManager: FalsingManager @@ -159,6 +161,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { handler, activityStarter, broadcastDispatcher, + broadcastSender, uiEventLogger, falsingManager, telephonyListenerManager, diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 7f103144b7fb..5ef1008afad5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -281,9 +281,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ void onDoubleTapAndHold(int displayId); - void requestImeLocked(AccessibilityServiceConnection connection); + void requestImeLocked(AbstractAccessibilityServiceConnection connection); - void unbindImeLocked(AccessibilityServiceConnection connection); + void unbindImeLocked(AbstractAccessibilityServiceConnection connection); } public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, @@ -387,7 +387,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0; mRequestAccessibilityButton = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; - // TODO(b/218193835): request ime when ime flag is set and clean up when ime flag is unset mRequestImeApis = (info.flags & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0; } @@ -439,6 +438,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // If the XML manifest had data to configure the service its info // should be already set. In such a case update only the dynamically // configurable properties. + boolean oldRequestIme = mRequestImeApis; AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo; if (oldInfo != null) { oldInfo.updateDynamicallyConfigurableProperties(mIPlatformCompat, info); @@ -447,6 +447,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ setDynamicallyConfigurableProperties(info); } mSystemSupport.onClientChangeLocked(true); + if (!oldRequestIme && mRequestImeApis) { + mSystemSupport.requestImeLocked(this); + } else if (oldRequestIme && !mRequestImeApis) { + mSystemSupport.unbindImeLocked(this); + } } } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 0ea087d6de0f..249ee16afaae 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -4305,7 +4305,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void requestImeLocked(AccessibilityServiceConnection connection) { + public void requestImeLocked(AbstractAccessibilityServiceConnection connection) { mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::createSessionForConnection, this, connection)); mMainHandler.sendMessage(obtainMessage( @@ -4313,12 +4313,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void unbindImeLocked(AccessibilityServiceConnection connection) { + public void unbindImeLocked(AbstractAccessibilityServiceConnection connection) { mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::unbindInputForConnection, this, connection)); } - private void createSessionForConnection(AccessibilityServiceConnection connection) { + private void createSessionForConnection(AbstractAccessibilityServiceConnection connection) { synchronized (mLock) { if (mInputSessionRequested) { connection.createImeSessionLocked(); @@ -4326,7 +4326,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void bindAndStartInputForConnection(AccessibilityServiceConnection connection) { + private void bindAndStartInputForConnection(AbstractAccessibilityServiceConnection connection) { synchronized (mLock) { if (mInputBinding != null) { connection.bindInputLocked(mInputBinding); @@ -4336,7 +4336,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void unbindInputForConnection(AccessibilityServiceConnection connection) { + private void unbindInputForConnection(AbstractAccessibilityServiceConnection connection) { InputMethodManagerInternal.get().unbindAccessibilityFromCurrentClient(connection.mId); synchronized (mLock) { connection.unbindInputLocked(); diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java index b1f572d2a364..ac2d1dd95da1 100644 --- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java +++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchManagerService.java @@ -129,7 +129,7 @@ public class CloudSearchManagerService extends @Override public void search(@NonNull SearchRequest searchRequest, @NonNull ICloudSearchManagerCallback callBack) { - searchRequest.setSource( + searchRequest.setCallerPackageName( mContext.getPackageManager().getNameForUid(Binder.getCallingUid())); runForUser("search", (service) -> { synchronized (service.mLock) { diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index c9d704084c19..2068e6d380b7 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -45,6 +45,7 @@ import android.util.SparseArray; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.pm.PackageList; import com.android.server.pm.PackageSetting; +import com.android.server.pm.dex.DynamicCodeLogger; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageApi; import com.android.server.pm.pkg.PackageState; @@ -68,6 +69,7 @@ import java.util.function.Consumer; * @hide Only for use within the system server. */ public abstract class PackageManagerInternal { + @IntDef(prefix = "PACKAGE_", value = { PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, @@ -902,6 +904,11 @@ public abstract class PackageManagerInternal { public abstract void freeStorage(String volumeUuid, long bytes, @StorageManager.AllocateFlags int flags) throws IOException; + /** + * Blocking call to clear all cached app data above quota. + */ + public abstract void freeAllAppCacheAboveQuota(@NonNull String volumeUuid) throws IOException; + /** Returns {@code true} if the specified component is enabled and matches the given flags. */ public abstract boolean isEnabledAndMatches(@NonNull ParsedMainComponent component, @PackageManager.ComponentInfoFlagsBits long flags, int userId); @@ -1362,4 +1369,8 @@ public abstract class PackageManagerInternal { */ @NonNull public abstract PackageDataSnapshot snapshot(); + + public abstract void shutdown(); + + public abstract DynamicCodeLogger getDynamicCodeLogger(); } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index b59cd4c0f212..1a39ffa393b3 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -1166,9 +1166,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub { final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); try { if (allowlist) { - cm.updateMeteredNetworkAllowList(uid, enable); + if (enable) { + cm.addUidToMeteredNetworkAllowList(uid); + } else { + cm.removeUidFromMeteredNetworkAllowList(uid); + } } else { - cm.updateMeteredNetworkDenyList(uid, enable); + if (enable) { + cm.addUidToMeteredNetworkDenyList(uid); + } else { + cm.removeUidFromMeteredNetworkDenyList(uid); + } } synchronized (mRulesLock) { if (enable) { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 9391b18acea6..7075ed34dfb4 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -75,7 +75,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ProviderInfo; import android.content.pm.UserInfo; -import android.content.res.Configuration; import android.content.res.ObbInfo; import android.database.ContentObserver; import android.net.Uri; @@ -124,7 +123,6 @@ import android.provider.Downloads; import android.provider.MediaStore; import android.provider.Settings; import android.service.storage.ExternalStorageService; -import android.sysprop.VoldProperties; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -1407,39 +1405,6 @@ class StorageManagerService extends IStorageManager.Stub private void handleDaemonConnected() { initIfBootedAndConnected(); resetIfBootedAndConnected(); - - // On an encrypted device we can't see system properties yet, so pull - // the system locale out of the mount service. - if ("".equals(VoldProperties.encrypt_progress().orElse(""))) { - copyLocaleFromMountService(); - } - } - - private void copyLocaleFromMountService() { - String systemLocale; - try { - systemLocale = getField(StorageManager.SYSTEM_LOCALE_KEY); - } catch (RemoteException e) { - return; - } - if (TextUtils.isEmpty(systemLocale)) { - return; - } - - Slog.d(TAG, "Got locale " + systemLocale + " from mount service"); - Locale locale = Locale.forLanguageTag(systemLocale); - Configuration config = new Configuration(); - config.setLocale(locale); - try { - ActivityManager.getService().updatePersistentConfigurationWithAttribution(config, - mContext.getOpPackageName(), mContext.getAttributionTag()); - } catch (RemoteException e) { - Slog.e(TAG, "Error setting system locale from mount service", e); - } - - // Temporary workaround for http://b/17945169. - Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service"); - SystemProperties.set("persist.sys.locale", locale.toLanguageTag()); } private final IVoldListener mListener = new IVoldListener.Stub() { @@ -3670,11 +3635,20 @@ class StorageManagerService extends IStorageManager.Stub mInstaller.tryMountDataMirror(volumeUuid); } } - } catch (RemoteException | Installer.InstallerException e) { + } catch (Exception e) { Slog.wtf(TAG, e); // Make sure to re-throw this exception; we must not ignore failure // to prepare the user storage as it could indicate that encryption // wasn't successfully set up. + // + // Very unfortunately, these errors need to be ignored for broken + // users that already existed on-disk from older Android versions. + UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); + if (umInternal.shouldIgnorePrepareStorageErrors(userId)) { + Slog.wtf(TAG, "ignoring error preparing storage for existing user " + userId + + "; device may be insecure!"); + return; + } throw new RuntimeException(e); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 40ab0c07d166..58fa9fb28a78 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2013,10 +2013,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return; } - ApnSetting apnSetting = preciseState.getApnSetting(); - synchronized (mRecords) { - if (validatePhoneId(phoneId)) { + if (validatePhoneId(phoneId) && preciseState.getApnSetting() != null) { Pair<Integer, ApnSetting> key = Pair.create(preciseState.getTransportType(), preciseState.getApnSetting()); PreciseDataConnectionState oldState = mPreciseDataConnectionStates.get(phoneId) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 47f9fd535801..95e35ef6cf32 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -79,6 +79,7 @@ import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE; import static android.os.Process.ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS; import static android.os.Process.ZYGOTE_PROCESS; import static android.os.Process.getTotalMemory; +import static android.os.Process.isSdkSandboxUid; import static android.os.Process.isThreadInProcess; import static android.os.Process.killProcess; import static android.os.Process.killProcessQuiet; @@ -3779,7 +3780,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (mProcLock) { mProcessList.killPackageProcessesLSP(packageName, appId, targetUserId, ProcessList.SERVICE_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_UNKNOWN, "kill background"); + ApplicationExitInfo.SUBREASON_KILL_BACKGROUND, "kill background"); } } } @@ -3809,7 +3810,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.killPackageProcessesLSP(null /* packageName */, -1 /* appId */, UserHandle.USER_ALL, ProcessList.CACHED_APP_MIN_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_UNKNOWN, + ApplicationExitInfo.SUBREASON_KILL_BACKGROUND, "kill all background"); } @@ -4351,7 +4352,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.INVALID_ADJ, true, false, true, false, true /* setRemoved */, false, ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_UNKNOWN, + ApplicationExitInfo.SUBREASON_STOP_APP, "fully stop " + packageName + "/" + userId + " by user request"); } @@ -4403,7 +4404,7 @@ public class ActivityManagerService extends IActivityManager.Stub evenPersistent, true /* setRemoved */, uninstalling, packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED : ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_UNKNOWN, + ApplicationExitInfo.SUBREASON_FORCE_STOP, (packageName == null ? ("stop user " + userId) : ("stop " + packageName)) + " due to " + reason); } @@ -8561,6 +8562,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (process.info.isInstantApp()) { sb.append("Instant-App: true\n"); } + if (isSdkSandboxUid(process.uid)) { + sb.append("SdkSandbox: true\n"); + } } } @@ -9752,7 +9756,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (info.deniedPermissions != null) { for (int j = 0; j < info.deniedPermissions.size(); j++) { pw.print(" deny: "); - pw.println(info.deniedPermissions.valueAt(i)); + pw.println(info.deniedPermissions.valueAt(j)); } } } @@ -13632,7 +13636,7 @@ public class ActivityManagerService extends IActivityManager.Stub UserHandle.getAppId(extraUid), userId, ProcessList.INVALID_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_UNKNOWN, + ApplicationExitInfo.SUBREASON_PACKAGE_UPDATE, "change " + ssp); } } @@ -16361,7 +16365,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (pr.mState.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND && pr.mReceivers.numberOfCurReceivers() == 0) { pr.killLocked("remove task", ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_UNKNOWN, true); + ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); } else { // We delay killing processes that are not in the background or running a // receiver. @@ -16998,7 +17002,17 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void addPendingTopUid(int uid, int pid) { - mPendingStartActivityUids.add(uid, pid); + final boolean isNewPending = mPendingStartActivityUids.add(uid, pid); + // If the next top activity is in cached and frozen mode, WM should raise its priority + // to unfreeze it. This is done by calling AMS.updateOomAdj that will lower its oom adj. + // However, WM cannot hold the AMS clock here so the updateOomAdj operation is performed + // in a separate thread asynchronously. Therefore WM can't guarantee AMS will unfreeze + // next top activity on time. This race will fail the following binder transactions WM + // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this + // workaround can be removed. (b/213288355) + if (isNewPending && mOomAdjuster != null) { // It can be null in unit test. + mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid); + } } @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 28b807c5d30b..2b61e7fd2752 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -343,6 +343,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSetStopUserOnSwitch(pw); case "set-bg-abusive-uids": return runSetBgAbusiveUids(pw); + case "list-bg-exemptions-config": + return runListBgExemptionsConfig(pw); default: return handleDefaultCommands(cmd); } @@ -3292,6 +3294,19 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + private int runListBgExemptionsConfig(PrintWriter pw) throws RemoteException { + final ArraySet<String> sysConfigs = mInternal.mAppRestrictionController + .mBgRestrictionExemptioFromSysConfig; + if (sysConfigs != null) { + for (int i = 0, size = sysConfigs.size(); i < size; i++) { + pw.print(sysConfigs.valueAt(i)); + pw.print(' '); + } + pw.println(); + } + return 0; + } + private Resources getResources(PrintWriter pw) throws RemoteException { // system resources does not contain all the device configuration, construct it manually. Configuration config = mInterface.getConfiguration(); diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 7579d2bc78fd..7cd45fed1498 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -1217,7 +1217,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mDefaultBgCurrentDrainBgRestrictedThreshold = isLowRamDeviceStatic() ? val[1] : val[0]; mDefaultBgCurrentDrainWindowMs = resources.getInteger( - R.integer.config_bg_current_drain_window); + R.integer.config_bg_current_drain_window) * 1_000; val = getFloatArray(resources.obtainTypedArray( R.array.config_bg_current_drain_high_threshold_to_restricted_bucket)); mDefaultBgCurrentDrainRestrictedBucketHighThreshold = @@ -1227,9 +1227,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mDefaultBgCurrentDrainBgRestrictedHighThreshold = isLowRamDeviceStatic() ? val[1] : val[0]; mDefaultBgCurrentDrainMediaPlaybackMinDuration = resources.getInteger( - R.integer.config_bg_current_drain_media_playback_min_duration); + R.integer.config_bg_current_drain_media_playback_min_duration) * 1_000; mDefaultBgCurrentDrainLocationMinDuration = resources.getInteger( - R.integer.config_bg_current_drain_location_min_duration); + R.integer.config_bg_current_drain_location_min_duration) * 1_000; mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled = resources.getBoolean( R.bool.config_bg_current_drain_event_duration_based_threshold_enabled); mDefaultCurrentDrainTypesToRestrictedBucket = resources.getInteger( diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 6e6cb879ae4f..dc8403aea1b3 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -182,7 +182,7 @@ public final class AppRestrictionController { /** * Whether or not to show the foreground service manager on tapping notifications. */ - private static final boolean ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER = false; + private static final boolean ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER = true; private final Context mContext; private final HandlerThread mBgHandlerThread; @@ -241,7 +241,7 @@ public final class AppRestrictionController { /** * The pre-config packages that are exempted from the background restrictions. */ - private ArraySet<String> mBgRestrictionExemptioFromSysConfig; + ArraySet<String> mBgRestrictionExemptioFromSysConfig; /** * Lock specifically for bookkeeping around the carrier-privileged app set. @@ -1595,8 +1595,8 @@ public final class AppRestrictionController { cancelRequestBgRestrictedIfNecessary(packageName, uid); final Intent newIntent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER); newIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - mContext.sendBroadcastAsUser(newIntent, - UserHandle.of(UserHandle.getUserId(uid))); + // Task manager runs in SystemUI, which is SYSTEM user only. + mContext.sendBroadcastAsUser(newIntent, UserHandle.SYSTEM); break; } } @@ -1670,9 +1670,10 @@ public final class AppRestrictionController { if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER) { final Intent intent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER); intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + // Task manager runs in SystemUI, which is SYSTEM user only. pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, - UserHandle.of(UserHandle.getUserId(uid))); + UserHandle.SYSTEM); } else { final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL); intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null)); @@ -1750,7 +1751,7 @@ public final class AppRestrictionController { SYSTEM_UID, UserHandle.getUserId(uid)); final String title = mContext.getString(titleRes); final String message = mContext.getString(messageRes, - ai != null ? pm.getText(packageName, ai.labelRes, ai) : packageName); + ai != null ? ai.loadLabel(pm) : packageName); final Icon icon = ai != null ? Icon.createWithResource(packageName, ai.icon) : null; postNotification(notificationId, packageName, uid, title, message, icon, pendingIntent, diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index b18d390476f0..ff569a681a4e 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -183,6 +183,8 @@ public final class CachedAppOptimizer { private final ActivityManagerGlobalLock mProcLock; + private final Object mFreezerLock = new Object(); + private final OnPropertiesChangedListener mOnFlagsChangedListener = new OnPropertiesChangedListener() { @Override @@ -949,8 +951,8 @@ public final class CachedAppOptimizer { } } - @GuardedBy({"mAm", "mProcLock"}) - void unfreezeAppLSP(ProcessRecord app) { + @GuardedBy({"mAm", "mProcLock", "mFreezerLock"}) + void unfreezeAppInternalLSP(ProcessRecord app) { final int pid = app.getPid(); final ProcessCachedOptimizerRecord opt = app.mOptRecord; if (opt.isPendingFreeze()) { @@ -1035,6 +1037,42 @@ public final class CachedAppOptimizer { } } + @GuardedBy({"mAm", "mProcLock"}) + void unfreezeAppLSP(ProcessRecord app) { + synchronized (mFreezerLock) { + unfreezeAppInternalLSP(app); + } + } + + /** + * This quick function works around the race condition between WM/ATMS and AMS, allowing + * the former to directly unfreeze a frozen process before the latter runs updateOomAdj. + * After the race issue is solved, this workaround can be removed. (b/213288355) + * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app. + * @param pid pid of the process to be unfrozen + */ + void unfreezeProcess(int pid) { + synchronized (mFreezerLock) { + ProcessRecord app = mFrozenProcesses.get(pid); + if (app == null) { + return; + } + Slog.d(TAG_AM, "quick sync unfreeze " + pid); + try { + freezeBinder(pid, false); + } catch (RuntimeException e) { + Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid); + return; + } + + try { + Process.setProcessFrozen(pid, app.uid, false); + } catch (Exception e) { + Slog.e(TAG_AM, "Unable to quick unfreeze " + pid); + } + } + } + /** * To be called when the given app is killed. */ @@ -1464,8 +1502,6 @@ public final class CachedAppOptimizer { return; } - Slog.d(TAG_AM, "froze " + pid + " " + name); - EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name); // See above for why we're not taking mPhenotypeFlagLock here diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index e0d333ee8f6a..7ed3dcf40f65 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2561,7 +2561,7 @@ public class OomAdjuster { if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0 && state.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND) { app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_UNKNOWN, true); + ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); success = false; } else { int processGroup; diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java index 802ea001e28c..455c75b88218 100644 --- a/services/core/java/com/android/server/am/PendingStartActivityUids.java +++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java @@ -46,10 +46,13 @@ final class PendingStartActivityUids { mContext = context; } - synchronized void add(int uid, int pid) { + /** Returns {@code true} if the uid is put to the pending array. Otherwise it existed. */ + synchronized boolean add(int uid, int pid) { if (mPendingUids.get(uid) == null) { mPendingUids.put(uid, new Pair<>(pid, SystemClock.elapsedRealtime())); + return true; } + return false; } synchronized void delete(int uid) { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 6e14203774a1..48ca59dc4c64 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1890,7 +1890,7 @@ public final class ProcessList { /** Return true if the client app for the SDK sandbox process is debuggable. */ private boolean isAppForSdkSandboxDebuggable(ProcessRecord sandboxProcess) { // TODO (b/221004701) use client app process name - final int appUid = Process.sdkSandboxToAppUid(sandboxProcess.uid); + final int appUid = Process.getAppUidForSdkSandboxUid(sandboxProcess.uid); IPackageManager pm = mService.getPackageManager(); try { String[] packages = pm.getPackagesForUid(appUid); diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java index 0abab6aafe73..a76eb8f1e55d 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -18,6 +18,7 @@ package com.android.server.app; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.content.Context; import android.content.Intent; @@ -29,6 +30,7 @@ import android.service.games.IGameSessionService; import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ScreenshotHelper; import com.android.server.LocalServices; import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration; import com.android.server.wm.WindowManagerInternal; @@ -51,11 +53,13 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide mContext, new GameClassifierImpl(mContext.getPackageManager()), ActivityManager.getService(), + LocalServices.getService(ActivityManagerInternal.class), ActivityTaskManager.getService(), (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE), LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, configuration), - new GameSessionServiceConnector(mContext, configuration)); + new GameSessionServiceConnector(mContext, configuration), + new ScreenshotHelper(mContext)); } private static final class GameServiceConnector extends ServiceConnector.Impl<IGameService> { diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index e4edf4e0f5f9..e9205230a4ad 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -21,15 +21,20 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; +import android.app.IProcessObserver; import android.app.TaskStackListener; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.Insets; import android.graphics.Rect; +import android.net.Uri; +import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.service.games.CreateGameSessionRequest; @@ -42,15 +47,19 @@ import android.service.games.IGameServiceController; import android.service.games.IGameSession; import android.service.games.IGameSessionController; import android.service.games.IGameSessionService; +import android.text.TextUtils; import android.util.Slog; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost.SurfacePackage; +import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ScreenshotHelper; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; import com.android.server.wm.WindowManagerService; @@ -59,6 +68,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; final class GameServiceProviderInstanceImpl implements GameServiceProviderInstance { private static final String TAG = "GameServiceProviderInstance"; @@ -136,12 +146,42 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan GameServiceProviderInstanceImpl.this.onTaskFocusChanged(taskId, focused); }); } + }; + + /** + * The TaskStackListener declared above gives us good visibility into game task lifecycle. + * However, it is possible for the Android system to kill all the processes associated with a + * game task (e.g., when the system is under memory pressure or reaches a background process + * limit). When this happens, the game task remains (and no TaskStackListener callbacks are + * invoked), but we would nonetheless want to destroy a game session associated with the task + * if this were to happen. + * + * This process observer gives us visibility into process lifecycles and lets us track all the + * processes associated with each package so that any game sessions associated with the package + * are destroyed if the process count for a given package reaches zero (most packages will + * have at most one task). If processes for a given package are started up again, the destroyed + * game sessions will be re-created. + */ + private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() { + @Override + public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) { + // This callback is used to track how many processes are running for a given package. + // Then, when a process dies, we will know if it was the only process running for that + // package and the associated game sessions should be destroyed. + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.onForegroundActivitiesChanged(pid); + }); + } + + @Override + public void onProcessDied(int pid, int uid) { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.onProcessDied(pid); + }); + } - // 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. + @Override + public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {} }; private final IGameServiceController mGameServiceController = @@ -185,9 +225,11 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private final Context mContext; private final GameClassifier mGameClassifier; private final IActivityManager mActivityManager; + private final ActivityManagerInternal mActivityManagerInternal; private final IActivityTaskManager mActivityTaskManager; private final WindowManagerService mWindowManagerService; private final WindowManagerInternal mWindowManagerInternal; + private final ScreenshotHelper mScreenshotHelper; private final ServiceConnector<IGameService> mGameServiceConnector; private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector; @@ -195,6 +237,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private final ConcurrentHashMap<Integer, GameSessionRecord> mGameSessions = new ConcurrentHashMap<>(); @GuardedBy("mLock") + private final ConcurrentHashMap<Integer, String> mPidToPackageMap = new ConcurrentHashMap<>(); + @GuardedBy("mLock") + private final ConcurrentHashMap<String, Integer> mPackageNameToProcessCountMap = + new ConcurrentHashMap<>(); + + @GuardedBy("mLock") private volatile boolean mIsRunning; GameServiceProviderInstanceImpl( @@ -203,21 +251,25 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @NonNull Context context, @NonNull GameClassifier gameClassifier, @NonNull IActivityManager activityManager, + @NonNull ActivityManagerInternal activityManagerInternal, @NonNull IActivityTaskManager activityTaskManager, @NonNull WindowManagerService windowManagerService, @NonNull WindowManagerInternal windowManagerInternal, @NonNull ServiceConnector<IGameService> gameServiceConnector, - @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) { + @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector, + @NonNull ScreenshotHelper screenshotHelper) { mUserHandle = userHandle; mBackgroundExecutor = backgroundExecutor; mContext = context; mGameClassifier = gameClassifier; mActivityManager = activityManager; + mActivityManagerInternal = activityManagerInternal; mActivityTaskManager = activityTaskManager; mWindowManagerService = windowManagerService; mWindowManagerInternal = windowManagerInternal; mGameServiceConnector = gameServiceConnector; mGameSessionServiceConnector = gameSessionServiceConnector; + mScreenshotHelper = screenshotHelper; } @Override @@ -253,6 +305,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan Slog.w(TAG, "Failed to register task stack listener", e); } + try { + mActivityManager.registerProcessObserver(mProcessObserver); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to register process observer", e); + } + mWindowManagerInternal.registerTaskSystemBarsListener(mTaskSystemBarsVisibilityListener); } @@ -264,6 +322,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mIsRunning = false; try { + mActivityManager.unregisterProcessObserver(mProcessObserver); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to unregister process observer", e); + } + + try { mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); } catch (RemoteException e) { Slog.w(TAG, "Failed to unregister task stack listener", e); @@ -586,6 +650,126 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } + private void onForegroundActivitiesChanged(int pid) { + synchronized (mLock) { + onForegroundActivitiesChangedLocked(pid); + } + } + + @GuardedBy("mLock") + private void onForegroundActivitiesChangedLocked(int pid) { + if (mPidToPackageMap.containsKey(pid)) { + // We are already tracking this pid, nothing to do. + return; + } + + final String packageName = mActivityManagerInternal.getPackageNameByPid(pid); + if (TextUtils.isEmpty(packageName)) { + // Game processes should always have a package name. + return; + } + + if (!gameSessionExistsForPackageNameLocked(packageName)) { + // We only need to track processes for tasks with game session records. + return; + } + + mPidToPackageMap.put(pid, packageName); + final int processCountForPackage = mPackageNameToProcessCountMap.getOrDefault(packageName, + 0) + 1; + mPackageNameToProcessCountMap.put(packageName, processCountForPackage); + + if (DEBUG) { + Slog.d(TAG, "onForegroundActivitiesChangedLocked: tracking pid " + pid + ", for " + + packageName + ". Process count for package: " + processCountForPackage); + } + + // If there are processes for the package, we may need to re-create game sessions + // that are associated with the package + if (processCountForPackage > 0) { + recreateEndedGameSessionsLocked(packageName); + } + } + + @GuardedBy("mLock") + private void recreateEndedGameSessionsLocked(String packageName) { + for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { + if (gameSessionRecord.isGameSessionEndedForProcessDeath() && packageName.equals( + gameSessionRecord.getComponentName().getPackageName())) { + if (DEBUG) { + Slog.d(TAG, + "recreateGameSessionsLocked(): re-creating game session for: " + + packageName + " with taskId: " + + gameSessionRecord.getTaskId()); + } + + final int taskId = gameSessionRecord.getTaskId(); + mGameSessions.put(taskId, GameSessionRecord.awaitingGameSessionRequest(taskId, + gameSessionRecord.getComponentName())); + createGameSessionLocked(gameSessionRecord.getTaskId()); + } + } + } + + private void onProcessDied(int pid) { + synchronized (mLock) { + onProcessDiedLocked(pid); + } + } + + @GuardedBy("mLock") + private void onProcessDiedLocked(int pid) { + final String packageName = mPidToPackageMap.remove(pid); + if (packageName == null) { + // We weren't tracking this process. + return; + } + + final Integer oldProcessCountForPackage = mPackageNameToProcessCountMap.get(packageName); + if (oldProcessCountForPackage == null) { + // This should never happen; we should have a process count for all tracked packages. + Slog.w(TAG, "onProcessDiedLocked(): Missing process count for package"); + return; + } + + final int processCountForPackage = oldProcessCountForPackage - 1; + mPackageNameToProcessCountMap.put(packageName, processCountForPackage); + + // If there are no more processes for the game, then we will terminate any game sessions + // running for the package. + if (processCountForPackage <= 0) { + endGameSessionsForPackageLocked(packageName); + } + } + + @GuardedBy("mLock") + private void endGameSessionsForPackageLocked(String packageName) { + for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { + if (gameSessionRecord.getGameSession() != null && packageName.equals( + gameSessionRecord.getComponentName().getPackageName())) { + if (DEBUG) { + Slog.d(TAG, "endGameSessionsForPackageLocked(): No more processes for " + + packageName + ", ending game session with taskId: " + + gameSessionRecord.getTaskId()); + } + mGameSessions.put(gameSessionRecord.getTaskId(), + gameSessionRecord.withGameSessionEndedOnProcessDeath()); + destroyGameSessionFromRecordLocked(gameSessionRecord); + } + } + } + + @GuardedBy("mLock") + private boolean gameSessionExistsForPackageNameLocked(String packageName) { + for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { + if (packageName.equals(gameSessionRecord.getComponentName().getPackageName())) { + return true; + } + } + + return false; + } + @Nullable private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); @@ -651,7 +835,26 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan Slog.w(TAG, "Could not get bitmap for id: " + taskId); callback.complete(GameScreenshotResult.createInternalErrorResult()); } else { - callback.complete(GameScreenshotResult.createSuccessResult(bitmap)); + final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle( + bitmap); + final RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); + if (runningTaskInfo == null) { + Slog.w(TAG, "Could not get running task info for id: " + taskId); + callback.complete(GameScreenshotResult.createInternalErrorResult()); + } + final Rect crop = runningTaskInfo.configuration.windowConfiguration.getBounds(); + final Consumer<Uri> completionConsumer = (uri) -> { + if (uri == null) { + callback.complete(GameScreenshotResult.createInternalErrorResult()); + } else { + callback.complete(GameScreenshotResult.createSuccessResult()); + } + }; + mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId, + mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(), + WindowManager.ScreenshotSource.SCREENSHOT_OTHER, + BackgroundThread.getHandler(), + completionConsumer); } }); } diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java index a241812f7868..74e538ef5011 100644 --- a/services/core/java/com/android/server/app/GameSessionRecord.java +++ b/services/core/java/com/android/server/app/GameSessionRecord.java @@ -35,6 +35,10 @@ final class GameSessionRecord { // A Game Session is created and attached. // GameSessionRecord.getGameSession() != null. GAME_SESSION_ATTACHED, + // A Game Session did exist for a given game task but was destroyed because the last process + // for the game died. + // GameSessionRecord.getGameSession() == null. + GAME_SESSION_ENDED_PROCESS_DEATH, } private final int mTaskId; @@ -99,6 +103,20 @@ final class GameSessionRecord { } @NonNull + public GameSessionRecord withGameSessionEndedOnProcessDeath() { + return new GameSessionRecord( + mTaskId, + State.GAME_SESSION_ENDED_PROCESS_DEATH, + mRootComponentName, + /* gameSession=*/ null, + /* surfacePackage=*/ null); + } + + public boolean isGameSessionEndedForProcessDeath() { + return mState == State.GAME_SESSION_ENDED_PROCESS_DEATH; + } + + @NonNull public int getTaskId() { return mTaskId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 653776b3ca65..79e3bf53acd3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -140,12 +140,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @Override public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { - // For UDFPS, notify SysUI that the illumination can be turned off. - // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE - if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) { - mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId())); - } - + // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off + // for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired + mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo)); super.onAcquired(acquiredInfo, vendorCode); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index c92d599d68e6..bb1fed0bfecc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -114,12 +114,16 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps @Override public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { + boolean acquiredGood = + acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; // For UDFPS, notify SysUI that the illumination can be turned off. // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE - if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD - && mSensorProps.isAnyUdfpsType()) { - vibrateSuccess(); - mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId())); + if (mSensorProps.isAnyUdfpsType()) { + if (acquiredGood) { + vibrateSuccess(); + } + mSensorOverlays.ifUdfps( + controller -> controller.onAcquired(getSensorId(), acquiredInfo)); } mSensorOverlays.ifUdfps(controller -> { diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index ecb43f601a86..7db99f1da130 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -184,6 +184,9 @@ public class CameraServiceProxy extends SystemService // Must be equal to number of CameraStreamProto in CameraActionEvent private static final int MAX_STREAM_STATISTICS = 5; + private static final float MIN_PREVIEW_FPS = 30.0f; + private static final float MAX_PREVIEW_FPS = 60.0f; + private final Context mContext; private final ServiceThread mHandlerThread; private final Handler mHandler; @@ -821,6 +824,7 @@ public class CameraServiceProxy extends SystemService Slog.v(TAG, "Stream " + i + ": width " + streamProtos[i].width + ", height " + streamProtos[i].height + ", format " + streamProtos[i].format + + ", maxPreviewFps " + streamStats.getMaxPreviewFps() + ", dataSpace " + streamProtos[i].dataSpace + ", usage " + streamProtos[i].usage + ", requestCount " + streamProtos[i].requestCount @@ -1015,6 +1019,11 @@ public class CameraServiceProxy extends SystemService return false; } + private float getMinFps(CameraSessionStats cameraState) { + float maxFps = cameraState.getMaxPreviewFps(); + return Math.max(Math.min(maxFps, MAX_PREVIEW_FPS), MIN_PREVIEW_FPS); + } + private void updateActivityCount(CameraSessionStats cameraState) { String cameraId = cameraState.getCameraId(); int newCameraState = cameraState.getNewCameraState(); @@ -1068,7 +1077,9 @@ public class CameraServiceProxy extends SystemService if (!alreadyActivePackage) { WindowManagerInternal wmi = LocalServices.getService(WindowManagerInternal.class); - wmi.addNonHighRefreshRatePackage(clientName); + float minFps = getMinFps(cameraState); + wmi.addRefreshRateRangeForPackage(clientName, + minFps, MAX_PREVIEW_FPS); } // Update activity events @@ -1107,7 +1118,7 @@ public class CameraServiceProxy extends SystemService if (!stillActivePackage) { WindowManagerInternal wmi = LocalServices.getService(WindowManagerInternal.class); - wmi.removeNonHighRefreshRatePackage(clientName); + wmi.removeRefreshRateRangeForPackage(clientName); } } diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java index 11c451e01d4c..28c7cad3b184 100644 --- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java +++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java @@ -54,8 +54,8 @@ class EmulatorClipboardMonitor implements Consumer<ClipData> { return bits; } - private boolean isPipeOpened() { - return mPipe != null; + private synchronized FileDescriptor getPipeFD() { + return mPipe; } private synchronized boolean openPipe() { @@ -107,14 +107,16 @@ class EmulatorClipboardMonitor implements Consumer<ClipData> { return msg; } - private void sendMessage(final byte[] msg) throws ErrnoException, InterruptedIOException { + private static void sendMessage( + final FileDescriptor fd, + final byte[] msg) throws ErrnoException, InterruptedIOException { final byte[] lengthBits = new byte[4]; final ByteBuffer bb = ByteBuffer.wrap(lengthBits); bb.order(ByteOrder.LITTLE_ENDIAN); bb.putInt(msg.length); - Os.write(mPipe, lengthBits, 0, lengthBits.length); - Os.write(mPipe, msg, 0, msg.length); + Os.write(fd, lengthBits, 0, lengthBits.length); + Os.write(fd, msg, 0, msg.length); } EmulatorClipboardMonitor(final Consumer<ClipData> setAndroidClipboard) { @@ -162,17 +164,22 @@ class EmulatorClipboardMonitor implements Consumer<ClipData> { } private void setHostClipboardImpl(final String value) { - if (LOG_CLIBOARD_ACCESS) { - Slog.i(TAG, "Setting the host clipboard to '" + value + "'"); - } + final FileDescriptor pipeFD = getPipeFD(); - try { - if (isPipeOpened()) { - sendMessage(value.getBytes()); - } - } catch (ErrnoException | InterruptedIOException e) { - Slog.e(TAG, "Failed to set host clipboard " + e.getMessage()); - } catch (IllegalArgumentException e) { + if (pipeFD != null) { + Thread t = new Thread(() -> { + if (LOG_CLIBOARD_ACCESS) { + Slog.i(TAG, "Setting the host clipboard to '" + value + "'"); + } + + try { + sendMessage(pipeFD, value.getBytes()); + } catch (ErrnoException | InterruptedIOException e) { + Slog.e(TAG, "Failed to set host clipboard " + e.getMessage()); + } catch (IllegalArgumentException e) { + } + }); + t.start(); } } } diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 6cb3b3b6740d..e6fd40902386 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -32,6 +32,7 @@ import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethod; @@ -108,10 +109,10 @@ final class IInputMethodInvoker { @AnyThread void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, int configChanges, boolean stylusHwSupported, - boolean shouldShowImeSwitcherWhenImeIsShown) { + @InputMethodNavButtonFlags int navButtonFlags) { try { mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported, - shouldShowImeSwitcherWhenImeIsShown); + navButtonFlags); } catch (RemoteException e) { logRemoteException(e); } @@ -147,20 +148,19 @@ final class IInputMethodInvoker { @AnyThread void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, - boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) { + boolean restarting, @InputMethodNavButtonFlags int navButtonFlags) { try { mTarget.startInput(startInputToken, inputContext, attribute, restarting, - shouldShowImeSwitcherWhenImeIsShown); + navButtonFlags); } catch (RemoteException e) { logRemoteException(e); } } @AnyThread - void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown) { + void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { try { - mTarget.onShouldShowImeSwitcherWhenImeIsShownChanged( - shouldShowImeSwitcherWhenImeIsShown); + mTarget.onNavButtonFlagsChanged(navButtonFlags); } catch (RemoteException e) { logRemoteException(e); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 882e2e397d2c..77dcbd3e9277 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -156,6 +156,7 @@ import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; +import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; @@ -335,6 +336,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private final HandwritingModeController mHwController; + @GuardedBy("ImfLock.class") + @Nullable + private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes; + static class SessionState { final ClientState client; final IInputMethodInvoker method; @@ -1723,11 +1728,52 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") + private void recreateImeDrawsImeNavBarResIfNecessary(@UserIdInt int targetUserId) { + // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the + // profile parent user. + // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups. + final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId); + if (mImeDrawsImeNavBarRes != null + && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) { + mImeDrawsImeNavBarRes.close(); + mImeDrawsImeNavBarRes = null; + } + if (mImeDrawsImeNavBarRes == null) { + final Context userContext; + if (mContext.getUserId() == profileParentUserId) { + userContext = mContext; + } else { + userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId), + 0 /* flags */); + } + mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext, + com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> { + synchronized (ImfLock.class) { + if (resource == mImeDrawsImeNavBarRes) { + sendOnNavButtonFlagsChangedLocked(); + } + } + }); + } + } + + @NonNull + private static PackageManager getPackageManagerForUser(@NonNull Context context, + @UserIdInt int userId) { + return context.getUserId() == userId + ? context.getPackageManager() + : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */) + .getPackageManager(); + } + + @GuardedBy("ImfLock.class") private void switchUserOnHandlerLocked(@UserIdInt int newUserId, IInputMethodClient clientToBeReset) { if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId + " currentUserId=" + mSettings.getCurrentUserId()); + recreateImeDrawsImeNavBarResIfNecessary(newUserId); + // ContentObserver should be registered again when the user is changed mSettingsObserver.registerContentObserverLocked(newUserId); @@ -1764,9 +1810,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub updateFromSettingsLocked(true); if (initialUserSwitch) { - InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, - mSettings.getEnabledInputMethodListLocked(), newUserId, - mContext.getBasePackageName()); + InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( + getPackageManagerForUser(mContext, newUserId), + mSettings.getEnabledInputMethodListLocked()); } if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId @@ -1832,6 +1878,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub }); } + recreateImeDrawsImeNavBarResIfNecessary(currentUserId); + mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); mSettingsObserver.registerContentObserverLocked(currentUserId); @@ -1853,9 +1901,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */); updateFromSettingsLocked(true); - InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, - mSettings.getEnabledInputMethodListLocked(), currentUserId, - mContext.getBasePackageName()); + InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( + getPackageManagerForUser(mContext, currentUserId), + mSettings.getEnabledInputMethodListLocked()); } } } @@ -2412,12 +2460,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub true /* direct */); } - final boolean shouldShowImeSwitcherWhenImeIsShown = - shouldShowImeSwitcherWhenImeIsShownLocked(); + @InputMethodNavButtonFlags + final int navButtonFlags = getInputMethodNavButtonFlagsLocked(); final SessionState session = mCurClient.curSession; setEnabledSessionLocked(session); session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting, - shouldShowImeSwitcherWhenImeIsShown); + navButtonFlags); if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null, @@ -2681,7 +2729,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + mCurTokenDisplayId); } inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), - configChanges, supportStylusHw, shouldShowImeSwitcherWhenImeIsShownLocked()); + configChanges, supportStylusHw, getInputMethodNavButtonFlagsLocked()); } @AnyThread @@ -2938,9 +2986,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - boolean shouldShowImeSwitcherWhenImeIsShownLocked() { - return shouldShowImeSwitcherLocked( + @InputMethodNavButtonFlags + private int getInputMethodNavButtonFlagsLocked() { + final boolean canImeDrawsImeNavBar = + mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get(); + final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked( InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE); + return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0) + | (shouldShowImeSwitcherWhenImeIsShown + ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0); } @GuardedBy("ImfLock.class") @@ -3203,7 +3257,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); - sendShouldShowImeSwitcherWhenImeIsShownLocked(); + sendOnNavButtonFlagsChangedLocked(); } @GuardedBy("ImfLock.class") @@ -4680,7 +4734,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HARD_KEYBOARD_SWITCH_CHANGED: mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1); synchronized (ImfLock.class) { - sendShouldShowImeSwitcherWhenImeIsShownLocked(); + sendOnNavButtonFlagsChangedLocked(); } return true; case MSG_SYSTEM_UNLOCK_USER: { @@ -4950,7 +5004,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); - sendShouldShowImeSwitcherWhenImeIsShownLocked(); + sendOnNavButtonFlagsChangedLocked(); // Notify InputMethodListListeners of the new installed InputMethods. final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList); @@ -4959,14 +5013,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - void sendShouldShowImeSwitcherWhenImeIsShownLocked() { + void sendOnNavButtonFlagsChangedLocked() { final IInputMethodInvoker curMethod = mBindingController.getCurMethod(); if (curMethod == null) { // No need to send the data if the IME is not yet bound. return; } - curMethod.onShouldShowImeSwitcherWhenImeIsShownChanged( - shouldShowImeSwitcherWhenImeIsShownLocked()); + curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked()); } @GuardedBy("ImfLock.class") @@ -6078,10 +6131,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodEnabledLocked(imi.getId(), true); } updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); - InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, - mSettings.getEnabledInputMethodListLocked(), - mSettings.getCurrentUserId(), - mContext.getBasePackageName()); + InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( + getPackageManagerForUser(mContext, mSettings.getCurrentUserId()), + mSettings.getEnabledInputMethodListLocked()); nextIme = mSettings.getSelectedInputMethod(); nextEnabledImes = mSettings.getEnabledInputMethodListLocked(); } else { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 98bde11ad517..c255fe14c03e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -203,7 +203,7 @@ final class InputMethodMenuController { attrs.setTitle("Select input method"); w.setAttributes(attrs); mService.updateSystemUiLocked(); - mService.sendShouldShowImeSwitcherWhenImeIsShownLocked(); + mService.sendOnNavButtonFlagsChangedLocked(); mSwitchingDialog.show(); } } @@ -239,7 +239,7 @@ final class InputMethodMenuController { mSwitchingDialogTitleView = null; mService.updateSystemUiLocked(); - mService.sendShouldShowImeSwitcherWhenImeIsShownLocked(); + mService.sendOnNavButtonFlagsChangedLocked(); mDialogBuilder = null; mIms = null; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index e619fff24d22..4633df23516d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -18,17 +18,16 @@ package com.android.server.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Build; import android.os.LocaleList; -import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -663,8 +662,9 @@ final class InputMethodUtils { return !subtype.isAuxiliary(); } - static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager, - List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) { + @UserHandleAware + static void setNonSelectedSystemImesDisabledUntilUsed(PackageManager packageManagerForUser, + List<InputMethodInfo> enabledImis) { if (DEBUG) { Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); } @@ -675,7 +675,8 @@ final class InputMethodUtils { } // Only the current spell checker should be treated as an enabled one. final SpellCheckerInfo currentSpellChecker = - TextServicesManagerInternal.get().getCurrentSpellCheckerForUser(userId); + TextServicesManagerInternal.get().getCurrentSpellCheckerForUser( + packageManagerForUser.getUserId()); for (final String packageName : systemImesDisabledUntilUsed) { if (DEBUG) { Slog.d(TAG, "check " + packageName); @@ -702,11 +703,12 @@ final class InputMethodUtils { } ApplicationInfo ai = null; try { - ai = packageManager.getApplicationInfo(packageName, - PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId); - } catch (RemoteException e) { + ai = packageManagerForUser.getApplicationInfo(packageName, + PackageManager.ApplicationInfoFlags.of( + PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS)); + } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName - + " userId=" + userId, e); + + " userId=" + packageManagerForUser.getUserId(), e); continue; } if (ai == null) { @@ -717,18 +719,18 @@ final class InputMethodUtils { if (!isSystemPackage) { continue; } - setDisabledUntilUsed(packageManager, packageName, userId, callingPackage); + setDisabledUntilUsed(packageManagerForUser, packageName); } } - private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName, - int userId, String callingPackage) { + private static void setDisabledUntilUsed(PackageManager packageManagerForUser, + String packageName) { final int state; try { - state = packageManager.getApplicationEnabledSetting(packageName, userId); - } catch (RemoteException e) { + state = packageManagerForUser.getApplicationEnabledSetting(packageName); + } catch (IllegalArgumentException e) { Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName - + " userId=" + userId, e); + + " userId=" + packageManagerForUser.getUserId(), e); return; } if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT @@ -737,12 +739,12 @@ final class InputMethodUtils { Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED"); } try { - packageManager.setApplicationEnabledSetting(packageName, + packageManagerForUser.setApplicationEnabledSetting(packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, - 0 /* newState */, userId, callingPackage); - } catch (RemoteException e) { + 0 /* newState */); + } catch (IllegalArgumentException e) { Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName - + " userId=" + userId + " callingPackage=" + callingPackage, e); + + " userId=" + packageManagerForUser.getUserId(), e); return; } } else { diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java new file mode 100644 index 000000000000..33e7a7621340 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static android.content.Intent.ACTION_OVERLAY_CHANGED; + +import android.annotation.AnyThread; +import android.annotation.BoolRes; +import android.annotation.NonNull; +import android.annotation.UserHandleAware; +import android.annotation.UserIdInt; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.PatternMatcher; +import android.util.Slog; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +/** + * A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is + * aware of per-user Runtime Resource Overlay (RRO). + */ +final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable { + private static final String TAG = "OverlayableSystemBooleanResourceWrapper"; + + private static final String SYSTEM_PACKAGE_NAME = "android"; + + @UserIdInt + private final int mUserId; + @NonNull + private final AtomicBoolean mValueRef; + @NonNull + private final AtomicReference<Runnable> mCleanerRef; + + /** + * Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID + * with a value change callback for the user associated with the {@link Context}. + * + * @param userContext The {@link Context} to be used to access the resource. This needs to be + * associated with the right user because the Runtime Resource Overlay (RRO) + * is per-user configuration. + * @param boolResId The resource ID to be queried. + * @param handler {@link Handler} to be used to dispatch {@code callback}. + * @param callback The callback to be notified when the specified value might be updated. + * The callback needs to take care of spurious wakeup. The value returned from + * {@link #get()} may look to be exactly the same as the previously read value + * e.g. when the value is changed from {@code false} to {@code true} to + * {@code false} in a very short period of time, because {@link #get()} always + * does volatile-read. + * @return New {@link OverlayableSystemBooleanResourceWrapper}. + */ + @NonNull + @UserHandleAware + static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext, + @BoolRes int boolResId, @NonNull Handler handler, + @NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) { + + // Note that we cannot fully trust this initial value due to the dead time between obtaining + // the value here and setting up a broadcast receiver for change callback below. + // We will refresh the value again later after setting up the change callback anyway. + final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId)); + + final AtomicReference<Runnable> cleanerRef = new AtomicReference<>(); + + final OverlayableSystemBooleanResourceWrapper object = + new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef, + cleanerRef); + + final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); + intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE); + intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL); + + final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final boolean newValue = evaluate(userContext, boolResId); + if (newValue != valueRef.getAndSet(newValue)) { + callback.accept(object); + } + } + }; + userContext.registerReceiver(broadcastReceiver, intentFilter, + null /* broadcastPermission */, handler, + Context.RECEIVER_NOT_EXPORTED); + cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver)); + + // Make sure that the initial observable value is obtained after the change callback is set. + valueRef.set(evaluate(userContext, boolResId)); + return object; + } + + private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId, + @NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) { + mUserId = userId; + mValueRef = valueRef; + mCleanerRef = cleanerRef; + } + + /** + * @return The boolean resource value. + */ + @AnyThread + boolean get() { + return mValueRef.get(); + } + + /** + * @return The user ID associated with this resource reader. + */ + @AnyThread + @UserIdInt + int getUserId() { + return mUserId; + } + + @AnyThread + private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) { + try { + return context.getPackageManager() + .getResourcesForApplication(SYSTEM_PACKAGE_NAME) + .getBoolean(boolResId); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e); + return false; + } + } + + /** + * Cleans up the callback. + */ + @AnyThread + @Override + public void close() { + final Runnable cleaner = mCleanerRef.getAndSet(null); + if (cleaner != null) { + cleaner.run(); + } + } +} diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 0f4648a3f0f8..3db11d84cbeb 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1800,7 +1800,10 @@ public class LockSettingsService extends ILockSettings.Stub { } private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) { - updateEncryptionPasswordIfNeeded(newCredential, userHandle); + if (userHandle == UserHandle.USER_SYSTEM && isDeviceEncryptionEnabled() && + shouldEncryptWithCredentials() && newCredential.isNone()) { + setCredentialRequiredToDecrypt(false); + } if (newCredential.isPattern()) { setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle); } @@ -1809,26 +1812,6 @@ public class LockSettingsService extends ILockSettings.Stub { } /** - * Update device encryption password if calling user is USER_SYSTEM and device supports - * encryption. - */ - private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) { - // Update the device encryption password. - if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) { - return; - } - if (!shouldEncryptWithCredentials()) { - updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); - return; - } - if (credential.isNone()) { - // Set the encryption password to default. - setCredentialRequiredToDecrypt(false); - } - updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential()); - } - - /** * Store the hash of the *current* password in the password history list, if device policy * enforces password history requirement. */ @@ -1942,34 +1925,6 @@ public class LockSettingsService extends ILockSettings.Stub { } } - /** Update the encryption password if it is enabled **/ - @Override - public void updateEncryptionPassword(final int type, final byte[] password) { - if (!hasSecureLockScreen() && password != null && password.length != 0) { - throw new UnsupportedOperationException( - "This operation requires the lock screen feature."); - } - if (!isDeviceEncryptionEnabled()) { - return; - } - final IBinder service = ServiceManager.getService("mount"); - if (service == null) { - Slog.e(TAG, "Could not find the mount service to update the encryption password"); - return; - } - - // TODO(b/120484642): This is a location where we still use a String for vold - String passwordString = password != null ? new String(password) : null; - mHandler.post(() -> { - IStorageManager storageManager = mInjector.getStorageManager(); - try { - storageManager.changeEncryptionPassword(type, passwordString); - } catch (RemoteException e) { - Slog.e(TAG, "Error changing encryption password", e); - } - }); - } - /** Register the given WeakEscrowTokenRemovedListener. */ @Override public boolean registerWeakEscrowTokenRemovedListener( diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java deleted file mode 100644 index 6b442a6a395e..000000000000 --- a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.logcat; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentSender; -import android.os.Bundle; -import android.os.ServiceManager; -import android.os.logcat.ILogcatManagerService; -import android.util.Slog; -import android.view.View; -import android.widget.TextView; - -import com.android.internal.R; -import com.android.internal.app.AlertActivity; -import com.android.internal.app.AlertController; - - -/** - * This dialog is shown to the user before an activity in a harmful app is launched. - * - * See {@code PackageManager.setLogcatAppInfo} for more info. - */ -public class LogAccessConfirmationActivity extends AlertActivity implements - DialogInterface.OnClickListener { - private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName(); - - private String mPackageName; - private IntentSender mTarget; - private final ILogcatManagerService mLogcatManagerService = - ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat")); - - private int mUid; - private int mGid; - private int mPid; - private int mFd; - - private static final String EXTRA_UID = "uid"; - private static final String EXTRA_GID = "gid"; - private static final String EXTRA_PID = "pid"; - private static final String EXTRA_FD = "fd"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final Intent intent = getIntent(); - mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); - mUid = intent.getIntExtra("uid", 0); - mGid = intent.getIntExtra("gid", 0); - mPid = intent.getIntExtra("pid", 0); - mFd = intent.getIntExtra("fd", 0); - - final AlertController.AlertParams p = mAlertParams; - p.mTitle = getString(R.string.log_access_confirmation_title); - p.mView = createView(); - - p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow); - p.mPositiveButtonListener = this; - p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny); - p.mNegativeButtonListener = this; - - mAlert.installContent(mAlertParams); - } - - private View createView() { - final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog, - null /*root*/); - ((TextView) view.findViewById(R.id.app_name_text)) - .setText(mPackageName); - ((TextView) view.findViewById(R.id.message)) - .setText(getIntent().getExtras().getString("body")); - return view; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - try { - mLogcatManagerService.approve(mUid, mGid, mPid, mFd); - } catch (Throwable t) { - Slog.e(TAG, "Could not start the LogcatManagerService.", t); - } - finish(); - break; - case DialogInterface.BUTTON_NEGATIVE: - try { - mLogcatManagerService.decline(mUid, mGid, mPid, mFd); - } catch (Throwable t) { - Slog.e(TAG, "Could not start the LogcatManagerService.", t); - } - finish(); - break; - } - } - - /** - * Create the Intent for a LogAccessConfirmationActivity. - */ - public static Intent createIntent(Context context, String targetPackageName, - IntentSender target, int uid, int gid, int pid, int fd) { - final Intent intent = new Intent(); - intent.setClass(context, LogAccessConfirmationActivity.class); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName); - intent.putExtra(EXTRA_UID, uid); - intent.putExtra(EXTRA_GID, gid); - intent.putExtra(EXTRA_PID, pid); - intent.putExtra(EXTRA_FD, fd); - - return intent; - } - -} diff --git a/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java new file mode 100644 index 000000000000..7116ca30c5a9 --- /dev/null +++ b/services/core/java/com/android/server/logcat/LogAccessDialogActivity.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.logcat; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.logcat.ILogcatManagerService; +import android.util.Slog; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import com.android.internal.R; + +/** + * Dialog responsible for obtaining user consent per-use log access + */ +public class LogAccessDialogActivity extends Activity implements + View.OnClickListener { + private static final String TAG = LogAccessDialogActivity.class.getSimpleName(); + private Context mContext; + + private final ILogcatManagerService mLogcatManagerService = + ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat")); + + private String mPackageName; + + private int mUid; + private int mGid; + private int mPid; + private int mFd; + private String mAlertTitle; + private AlertDialog.Builder mAlertDialog; + private AlertDialog mAlert; + + private static final int DIALOG_TIME_OUT = 300000; + private static final int MSG_DISMISS_DIALOG = 0; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = this; + + Intent intent = getIntent(); + mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); + mUid = intent.getIntExtra("com.android.server.logcat.uid", 0); + mGid = intent.getIntExtra("com.android.server.logcat.gid", 0); + mPid = intent.getIntExtra("com.android.server.logcat.pid", 0); + mFd = intent.getIntExtra("com.android.server.logcat.fd", 0); + mAlertTitle = getTitleString(mContext, mPackageName, mUid); + + if (mAlertTitle != null) { + + mAlertDialog = new AlertDialog.Builder(this); + mAlertDialog.setView(createView()); + + mAlert = mAlertDialog.create(); + mAlert.show(); + mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT); + + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mAlert != null && mAlert.isShowing()) { + mAlert.dismiss(); + } + mAlert = null; + } + + private Handler mHandler = new Handler() { + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_DISMISS_DIALOG: + if (mAlert != null) { + mAlert.dismiss(); + mAlert = null; + try { + mLogcatManagerService.decline(mUid, mGid, mPid, mFd); + } catch (RemoteException e) { + Slog.e(TAG, "Fails to call remote functions", e); + } + } + break; + + default: + break; + } + } + }; + + private String getTitleString(Context context, String callingPackage, int uid) { + PackageManager pm = context.getPackageManager(); + try { + return context.getString( + com.android.internal.R.string.log_access_confirmation_title, + pm.getApplicationInfoAsUser(callingPackage, + PackageManager.MATCH_DIRECT_BOOT_AUTO, + UserHandle.getUserId(uid)).loadLabel(pm)); + } catch (NameNotFoundException e) { + Slog.e(TAG, "App name is unknown.", e); + return null; + } + } + + private View createView() { + final View view = getLayoutInflater().inflate( + R.layout.log_access_user_consent_dialog_permission, null /*root*/); + + ((TextView) view.findViewById(R.id.log_access_dialog_title)) + .setText(mAlertTitle); + + Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button); + button_allow.setOnClickListener(this); + + Button button_deny = (Button) view.findViewById(R.id.log_access_dialog_deny_button); + button_deny.setOnClickListener(this); + + return view; + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.log_access_dialog_allow_button: + try { + mLogcatManagerService.approve(mUid, mGid, mPid, mFd); + } catch (RemoteException e) { + Slog.e(TAG, "Fails to call remote functions", e); + } + finish(); + break; + case R.id.log_access_dialog_deny_button: + try { + mLogcatManagerService.decline(mUid, mGid, mPid, mFd); + } catch (RemoteException e) { + Slog.e(TAG, "Fails to call remote functions", e); + } + finish(); + break; + } + } +} diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index 490e00e70f26..7b63fa24088b 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -18,15 +18,12 @@ package com.android.server.logcat; import android.annotation.NonNull; import android.app.ActivityManager; -import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManagerInternal; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Binder; import android.os.ILogd; import android.os.RemoteException; import android.os.ServiceManager; @@ -34,16 +31,14 @@ import android.os.UserHandle; import android.os.logcat.ILogcatManagerService; import android.util.Slog; -import com.android.internal.R; -import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.SystemService; -import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; + /** * Service responsible for managing the access to Logcat. */ @@ -54,43 +49,16 @@ public final class LogcatManagerService extends SystemService { private final BinderService mBinderService; private final ExecutorService mThreadExecutor; private ILogd mLogdService; - private NotificationManager mNotificationManager; private @NonNull ActivityManager mActivityManager; private ActivityManagerInternal mActivityManagerInternal; private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2; - private static int sUidImportanceListenerCount = 0; - private static final int AID_SHELL_UID = 2000; - - // TODO This allowlist is just a temporary workaround for the tests: - // FrameworksServicesTests - // PlatformRuleTests - // After adapting the test suites, the allowlist will be removed in - // the upcoming bug fix patches. - private static final String[] ALLOWABLE_TESTING_PACKAGES = { - "android.platform.test.rule.tests", - "com.android.frameworks.servicestests" - }; - - // TODO Same as the above ALLOWABLE_TESTING_PACKAGES. - private boolean isAllowableTestingPackage(int uid) { - PackageManager pm = mContext.getPackageManager(); - - String[] packageNames = pm.getPackagesForUid(uid); - - if (ArrayUtils.isEmpty(packageNames)) { - return false; - } - - for (String name : packageNames) { - Slog.e(TAG, "isAllowableTestingPackage: " + name); - - if (Arrays.asList(ALLOWABLE_TESTING_PACKAGES).contains(name)) { - return true; - } - } - - return false; - }; + private static final String TARGET_PACKAGE_NAME = "android"; + private static final String TARGET_ACTIVITY_NAME = + "com.android.server.logcat.LogAccessDialogActivity"; + private static final String EXTRA_UID = "com.android.server.logcat.uid"; + private static final String EXTRA_GID = "com.android.server.logcat.gid"; + private static final String EXTRA_PID = "com.android.server.logcat.pid"; + private static final String EXTRA_FD = "com.android.server.logcat.fd"; private final class BinderService extends ILogcatManagerService.Stub { @Override @@ -110,7 +78,7 @@ public final class LogcatManagerService extends SystemService { try { getLogdService().approve(uid, gid, pid, fd); } catch (RemoteException e) { - e.printStackTrace(); + Slog.e(TAG, "Fails to call remote functions", e); } } @@ -119,7 +87,7 @@ public final class LogcatManagerService extends SystemService { try { getLogdService().decline(uid, gid, pid, fd); } catch (RemoteException e) { - e.printStackTrace(); + Slog.e(TAG, "Fails to call remote functions", e); } } } @@ -133,46 +101,16 @@ public final class LogcatManagerService extends SystemService { } } - private String getBodyString(Context context, String callingPackage, int uid) { - PackageManager pm = context.getPackageManager(); - try { - return context.getString( - com.android.internal.R.string.log_access_confirmation_body, - pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO, - UserHandle.getUserId(uid)).loadLabel(pm)); - } catch (NameNotFoundException e) { - // App name is unknown. - return null; - } - } - - private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid, - int fd) { - + private void showDialog(int uid, int gid, int pid, int fd) { final ActivityManagerInternal activityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); PackageManager pm = mContext.getPackageManager(); String packageName = activityManagerInternal.getPackageNameByPid(pid); if (packageName != null) { - String notificationBody = getBodyString(mContext, packageName, uid); - - final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, - packageName, null, uid, gid, pid, fd); - - if (notificationBody == null) { - // Decline the logd access if the nofitication body is unknown - Slog.e(TAG, "Unknown notification body, declining the logd access"); - declineLogdAccess(uid, gid, pid, fd); - return; - } - - // TODO Next version will replace notification with dialogue - // per UX guidance. - generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody, - mIntent); + Intent mIntent = createIntent(packageName, uid, gid, pid, fd); + mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM); return; - } String[] packageNames = pm.getPackagesForUid(uid); @@ -186,115 +124,28 @@ public final class LogcatManagerService extends SystemService { String firstPackageName = packageNames[0]; - if (firstPackageName == null || firstPackageName.length() == 0) { + if (firstPackageName.isEmpty() || firstPackageName == null) { // Decline the logd access if the package name from uid is unknown Slog.e(TAG, "Unknown calling package name, declining the logd access"); declineLogdAccess(uid, gid, pid, fd); return; } - String notificationBody = getBodyString(mContext, firstPackageName, uid); - - final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, - firstPackageName, null, uid, gid, pid, fd); - - if (notificationBody == null) { - Slog.e(TAG, "Unknown notification body, declining the logd access"); - declineLogdAccess(uid, gid, pid, fd); - return; - } - - // TODO Next version will replace notification with dialogue - // per UX guidance. - generateNotificationWithBodyContent(notificationId, clientInfo, - notificationBody, mIntent); + final Intent mIntent = createIntent(firstPackageName, uid, gid, pid, fd); + mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM); } private void declineLogdAccess(int uid, int gid, int pid, int fd) { try { getLogdService().decline(uid, gid, pid, fd); - } catch (RemoteException ex) { - Slog.e(TAG, "Fails to call remote functions ", ex); - } - } - - private void generateNotificationWithBodyContent(int notificationId, String clientInfo, - String notificationBody, Intent intent) { - final Notification.Builder notificationBuilder = new Notification.Builder( - mContext, - SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY); - intent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.setIdentifier(String.valueOf(notificationId) + clientInfo); - intent.putExtra("body", notificationBody); - - notificationBuilder - .setSmallIcon(R.drawable.ic_info) - .setContentTitle( - mContext.getString(R.string.log_access_confirmation_title)) - .setContentText(notificationBody) - .setContentIntent( - PendingIntent.getActivity(mContext, 0, intent, - PendingIntent.FLAG_IMMUTABLE)) - .setTicker(mContext.getString(R.string.log_access_confirmation_title)) - .setOnlyAlertOnce(true) - .setAutoCancel(true); - mNotificationManager.notify(notificationId, notificationBuilder.build()); - } - - /** - * A class which watches an uid for background access and notifies the logdMonitor when - * the package status becomes foreground (importance change) - */ - private class UidImportanceListener implements ActivityManager.OnUidImportanceListener { - private final int mExpectedUid; - private final int mExpectedGid; - private final int mExpectedPid; - private final int mExpectedFd; - private int mExpectedImportance; - private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE; - - UidImportanceListener(int uid, int gid, int pid, int fd, int importance) { - mExpectedUid = uid; - mExpectedGid = gid; - mExpectedPid = pid; - mExpectedFd = fd; - mExpectedImportance = importance; - } - - @Override - public void onUidImportance(int uid, int importance) { - if (uid == mExpectedUid) { - mCurrentImportance = importance; - - /** - * 1) If the process status changes to foreground, send a notification - * for user consent. - * 2) If the process status remains background, we decline logd access request. - **/ - if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { - String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd); - sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid, - mExpectedFd); - mActivityManager.removeOnUidImportanceListener(this); - - synchronized (LogcatManagerService.this) { - sUidImportanceListenerCount--; - } - } else { - try { - getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd); - } catch (RemoteException ex) { - Slog.e(TAG, "Fails to call remote functions ", ex); - } - } - } + } catch (RemoteException e) { + Slog.e(TAG, "Fails to call remote functions", e); } } private static String getClientInfo(int uid, int gid, int pid, int fd) { return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID=" - + Integer.toString(pid) + " FD=" + Integer.toString(fd); + + Integer.toString(pid) + " FD=" + Integer.toString(fd); } private class LogdMonitor implements Runnable { @@ -338,18 +189,22 @@ public final class LogcatManagerService extends SystemService { try { getLogdService().approve(mUid, mGid, mPid, mFd); } catch (RemoteException e) { - e.printStackTrace(); + Slog.e(TAG, "Fails to call remote functions", e); } return; } - // TODO Temporarily approve all the requests to unblock testing failures. - try { - getLogdService().approve(mUid, mGid, mPid, mFd); - } catch (RemoteException e) { - e.printStackTrace(); + final int procState = mActivityManager.getUidImportance(Binder.getCallingUid()); + // If the process is foreground, send a notification for user consent + if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + showDialog(mUid, mGid, mPid, mFd); + } else { + /** + * If the process is background, decline the logd access. + **/ + declineLogdAccess(mUid, mGid, mPid, mFd); + return; } - return; } } } @@ -360,7 +215,6 @@ public final class LogcatManagerService extends SystemService { mBinderService = new BinderService(); mThreadExecutor = Executors.newCachedThreadPool(); mActivityManager = context.getSystemService(ActivityManager.class); - mNotificationManager = mContext.getSystemService(NotificationManager.class); } @Override @@ -375,4 +229,23 @@ public final class LogcatManagerService extends SystemService { private void addLogdService() { mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd")); } + + /** + * Create the Intent for LogAccessDialogActivity. + */ + public Intent createIntent(String targetPackageName, int uid, int gid, int pid, int fd) { + final Intent intent = new Intent(); + + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName); + intent.putExtra(EXTRA_UID, uid); + intent.putExtra(EXTRA_GID, gid); + intent.putExtra(EXTRA_PID, pid); + intent.putExtra(EXTRA_FD, fd); + + intent.setComponent(new ComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME)); + + return intent; + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c4731aa7b522..265ad7dee388 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1623,18 +1623,6 @@ public class NotificationManagerService extends SystemService { } }; - @VisibleForTesting - final IAppOpsCallback mAppOpsCallback = new IAppOpsCallback.Stub() { - @Override public void opChanged(int op, int uid, String packageName) { - if (mEnableAppSettingMigration) { - int opValue = mAppOps.checkOpNoThrow( - AppOpsManager.OP_POST_NOTIFICATION, uid, packageName); - boolean blocked = op != MODE_ALLOWED; - sendAppBlockStateChangedBroadcast(packageName, uid, blocked); - } - } - }; - private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -2133,12 +2121,6 @@ public class NotificationManagerService extends SystemService { mUsageStatsManagerInternal = usageStatsManagerInternal; mAppOps = appOps; mAppOpsService = iAppOps; - try { - mAppOpsService.startWatchingMode( - AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Could not register OP_POST_NOTIFICATION listener"); - } mAppUsageStats = appUsageStats; mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; @@ -3409,6 +3391,7 @@ public class NotificationManagerService extends SystemService { } mPermissionHelper.setNotificationPermission( pkg, UserHandle.getUserId(uid), enabled, true); + sendAppBlockStateChangedBroadcast(pkg, uid, !enabled); } else { synchronized (mNotificationLock) { boolean wasEnabled = mPreferencesHelper.getImportance(pkg, uid) @@ -3746,6 +3729,10 @@ public class NotificationManagerService extends SystemService { if (!hadChannel && hasChannel && !hasRequestedNotificationPermission && startingTaskId != ActivityTaskManager.INVALID_TASK_ID) { hasRequestedNotificationPermission = true; + if (mPermissionPolicyInternal == null) { + mPermissionPolicyInternal = + LocalServices.getService(PermissionPolicyInternal.class); + } mHandler.post(new ShowNotificationPermissionPromptRunnable(pkg, UserHandle.getUserId(uid), startingTaskId, mPermissionPolicyInternal)); @@ -3764,19 +3751,7 @@ public class NotificationManagerService extends SystemService { try { int uid = mPackageManager.getPackageUid(pkg, 0, UserHandle.getUserId(Binder.getCallingUid())); - List<ActivityManager.AppTask> tasks = mAtm.getAppTasks(pkg, uid); - for (int i = 0; i < tasks.size(); i++) { - ActivityManager.RecentTaskInfo task = tasks.get(i).getTaskInfo(); - if (mPermissionPolicyInternal == null) { - mPermissionPolicyInternal = - LocalServices.getService(PermissionPolicyInternal.class); - } - if (mPermissionPolicyInternal != null - && mPermissionPolicyInternal.canShowPermissionPromptForTask(task)) { - taskId = task.taskId; - break; - } - } + taskId = mAtm.getTaskToShowPermissionDialogOn(pkg, uid); } catch (RemoteException e) { // Do nothing } @@ -4068,13 +4043,12 @@ public class NotificationManagerService extends SystemService { @Override public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd( - String pkg, int userId) { + String pkg, int uid) { checkCallerIsSystem(); - if (!areNotificationsEnabledForPackage(pkg, - mPackageManagerInternal.getPackageUid(pkg, 0, userId))) { + if (!areNotificationsEnabledForPackage(pkg, uid)) { return ParceledListSlice.emptyList(); } - return mPreferencesHelper.getNotificationChannelsBypassingDnd(pkg, userId); + return mPreferencesHelper.getNotificationChannelsBypassingDnd(pkg, uid); } @Override diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index 6b9e374771ff..e551f1056b24 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -35,6 +35,7 @@ import android.util.ArrayMap; import android.util.Pair; import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.util.Collections; @@ -178,13 +179,16 @@ public final class PermissionHelper { boolean userSet, boolean reviewRequired) { assertFlag(); final long callingId = Binder.clearCallingIdentity(); - // Do not change fixed permissions, and do not change non-user set permissions that are - // granted by default, or granted by role. - if (isPermissionFixed(packageName, userId) - || (isPermissionGrantedByDefaultOrRole(packageName, userId) && !userSet)) { - return; - } try { + // Do not change the permission if the package doesn't request it, do not change fixed + // permissions, and do not change non-user set permissions that are granted by default, + // or granted by role. + if (!packageRequestsNotificationPermission(packageName, userId) + || isPermissionFixed(packageName, userId) + || (isPermissionGrantedByDefaultOrRole(packageName, userId) && !userSet)) { + return; + } + boolean currentlyGranted = mPmi.checkPermission(packageName, NOTIFICATION_PERMISSION, userId) != PackageManager.PERMISSION_DENIED; if (grant && !reviewRequired && !currentlyGranted) { @@ -278,6 +282,19 @@ public final class PermissionHelper { } } + private boolean packageRequestsNotificationPermission(String packageName, + @UserIdInt int userId) { + assertFlag(); + try { + String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, + userId).requestedPermissions; + return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION); + } catch (RemoteException e) { + Slog.e(TAG, "Could not reach system server", e); + } + return false; + } + private void assertFlag() { if (!mMigrationEnabled) { throw new IllegalStateException("Method called without checking flag value"); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 11858904a69a..1f7d65e6c604 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1669,14 +1669,14 @@ public class PreferencesHelper implements RankingConfig { } /** - * Gets all notification channels associated with the given pkg and userId that can bypass dnd + * Gets all notification channels associated with the given pkg and uid that can bypass dnd */ public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg, - int userId) { + int uid) { List<NotificationChannel> channels = new ArrayList<>(); synchronized (mPackagePreferences) { final PackagePreferences r = mPackagePreferences.get( - packagePreferencesKey(pkg, userId)); + packagePreferencesKey(pkg, uid)); if (r != null) { for (NotificationChannel channel : r.channels.values()) { if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) { diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index 2a6dd8410acb..b0d40efed690 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -112,7 +112,7 @@ public class ZenModeFiltering { } if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { if (consolidatedPolicy.allowRepeatCallers() - && REPEAT_CALLERS.isRepeat(context, extras)) { + && REPEAT_CALLERS.isRepeat(context, extras, null)) { ZenLog.traceMatchesCallFilter(true, "repeat caller"); return true; } @@ -229,7 +229,8 @@ public class ZenModeFiltering { } if (isCall(record)) { if (policy.allowRepeatCallers() - && REPEAT_CALLERS.isRepeat(mContext, extras(record))) { + && REPEAT_CALLERS.isRepeat( + mContext, extras(record), record.getPhoneNumbers())) { ZenLog.traceNotIntercepted(record, "repeatCaller"); return false; } @@ -350,6 +351,9 @@ public class ZenModeFiltering { private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>(); private int mThresholdMinutes; + // Record all people URIs in the extras bundle as well as the provided phoneNumbers set + // as callers. The phoneNumbers set is used to pass in any additional phone numbers + // associated with the people URIs as separately retrieved from contacts. private synchronized void recordCall(Context context, Bundle extras, ArraySet<String> phoneNumbers) { setThresholdMinutes(context); @@ -362,7 +366,13 @@ public class ZenModeFiltering { recordCallers(extraPeople, phoneNumbers, now); } - private synchronized boolean isRepeat(Context context, Bundle extras) { + // Determine whether any people in the provided extras bundle or phone number set is + // a repeat caller. The extras bundle contains the people associated with a specific + // notification, and will suffice for most callers; the phoneNumbers array may be used + // to additionally check any specific phone numbers previously retrieved from contacts + // associated with the people in the extras bundle. + private synchronized boolean isRepeat(Context context, Bundle extras, + ArraySet<String> phoneNumbers) { setThresholdMinutes(context); if (mThresholdMinutes <= 0 || extras == null) return false; final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); @@ -370,7 +380,7 @@ public class ZenModeFiltering { final long now = System.currentTimeMillis(); cleanUp(mTelCalls, now); cleanUp(mOtherCalls, now); - return checkCallers(context, extraPeople); + return checkCallers(context, extraPeople, phoneNumbers); } private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) { @@ -433,7 +443,31 @@ public class ZenModeFiltering { } } - private synchronized boolean checkCallers(Context context, String[] people) { + // helper function to check mTelCalls array for a number, and also check its decoded + // version + private synchronized boolean checkForNumber(String number, String defaultCountryCode) { + if (mTelCalls.containsKey(number)) { + // check directly via map first + return true; + } else { + // see if a number that matches via areSameNumber exists + String numberToCheck = Uri.decode(number); + if (numberToCheck != null) { + for (String prev : mTelCalls.keySet()) { + if (PhoneNumberUtils.areSamePhoneNumber( + numberToCheck, prev, defaultCountryCode)) { + return true; + } + } + } + } + return false; + } + + // Check whether anyone in the provided array of people URIs or phone number set matches a + // previously recorded phone call. + private synchronized boolean checkCallers(Context context, String[] people, + ArraySet<String> phoneNumbers) { // get the default country code for checking telephone numbers final String defaultCountryCode = context.getSystemService(TelephonyManager.class).getNetworkCountryIso(); @@ -443,20 +477,8 @@ public class ZenModeFiltering { final Uri uri = Uri.parse(person); if ("tel".equals(uri.getScheme())) { String number = uri.getSchemeSpecificPart(); - if (mTelCalls.containsKey(number)) { - // check directly via map first + if (checkForNumber(number, defaultCountryCode)) { return true; - } else { - // see if a number that matches via areSameNumber exists - String numberToCheck = Uri.decode(number); - if (numberToCheck != null) { - for (String prev : mTelCalls.keySet()) { - if (PhoneNumberUtils.areSamePhoneNumber( - numberToCheck, prev, defaultCountryCode)) { - return true; - } - } - } } } else { if (mOtherCalls.containsKey(person)) { @@ -464,6 +486,17 @@ public class ZenModeFiltering { } } } + + // also check any passed-in phone numbers + if (phoneNumbers != null) { + for (String num : phoneNumbers) { + if (checkForNumber(num, defaultCountryCode)) { + return true; + } + } + } + + // no matches return false; } } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 6a2b2d582458..76d3d233d49a 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -29,7 +29,6 @@ import android.apex.CompressedApexInfoList; import android.apex.IApexService; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.SigningDetails; import android.content.pm.parsing.result.ParseResult; @@ -834,7 +833,7 @@ public abstract class ApexManager { throw new RuntimeException(re); } catch (Exception e) { throw new PackageManagerException( - PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "apexd verification failed : " + e.getMessage()); } } @@ -861,7 +860,7 @@ public abstract class ApexManager { throw new RuntimeException(re); } catch (Exception e) { throw new PackageManagerException( - PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Failed to mark apexd session as ready : " + e.getMessage()); } } diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index c24bfec5fb43..5013570fa6b8 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -36,7 +36,6 @@ import android.os.storage.StorageManagerInternal; import android.os.storage.VolumeInfo; import android.security.AndroidKeyStoreMaintenance; import android.system.keystore2.Domain; -import android.system.keystore2.KeyDescriptor; import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -555,26 +554,6 @@ final class AppDataHelper { return prepareAppDataFuture; } - public void migrateKeyStoreData(int previousAppId, int appId) { - // If previous UID is system UID, declaring inheritKeyStoreKeys is not supported. - // Silently ignore the request to migrate keys. - if (previousAppId == Process.SYSTEM_UID) return; - - for (int userId : mPm.resolveUserIds(UserHandle.USER_ALL)) { - int srcUid = UserHandle.getUid(userId, previousAppId); - int destUid = UserHandle.getUid(userId, appId); - final KeyDescriptor[] keys = AndroidKeyStoreMaintenance.listEntries(Domain.APP, srcUid); - if (keys == null) continue; - for (final KeyDescriptor key : keys) { - KeyDescriptor dest = new KeyDescriptor(); - dest.domain = Domain.APP; - dest.nspace = destUid; - dest.alias = key.alias; - AndroidKeyStoreMaintenance.migrateKeyNamespace(key, dest); - } - } - } - void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) { if (pkg == null) { return; diff --git a/services/core/java/com/android/server/pm/AppIdSettingMap.java b/services/core/java/com/android/server/pm/AppIdSettingMap.java new file mode 100644 index 000000000000..bbef237507bf --- /dev/null +++ b/services/core/java/com/android/server/pm/AppIdSettingMap.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.os.Process; + +import com.android.server.utils.WatchedSparseArray; + +/** + * A wrapper over {@link WatchedSparseArray} that tracks the current maximum App ID. + */ +public class AppIdSettingMap extends WatchedSparseArray<SettingBase> { + private int mCurrentMaxAppId; + + @Override + public void put(int key, SettingBase value) { + if (key > mCurrentMaxAppId) { + mCurrentMaxAppId = key; + } + super.put(key, value); + } + + @Override + public AppIdSettingMap snapshot() { + AppIdSettingMap l = new AppIdSettingMap(); + snapshot(l, this); + return l; + } + + /** + * @return the maximum of all the App IDs that have been added to the map. 0 if map is empty. + */ + public int getCurrentMaxAppId() { + return mCurrentMaxAppId; + } + + /** + * @return the next available App ID that has not been added to the map + */ + public int getNextAvailableAppId() { + if (mCurrentMaxAppId == 0) { + // No app id has been added yet + return Process.FIRST_APPLICATION_UID; + } else { + return mCurrentMaxAppId + 1; + } + } +} diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 31df0a53eaa9..ecbb4a9d45a1 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -557,7 +557,7 @@ public final class BackgroundDexOptService { /** Gets the size of a package. */ private long getPackageSize(PackageManagerService pm, String pkg) { - PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); + PackageInfo info = pm.snapshotComputer().getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM); long size = 0; if (info != null && info.applicationInfo != null) { File path = Paths.get(info.applicationInfo.sourceDir).toFile(); diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index df7387dc946c..f1394d403bf7 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -119,14 +119,10 @@ public final class BroadcastHelper { intent.setPackage(targetPkg); } // Modify the UID when posting to other users - final String[] uidExtraNames = - { Intent.EXTRA_UID, Intent.EXTRA_PREVIOUS_UID, Intent.EXTRA_NEW_UID }; - for (String name : uidExtraNames) { - int uid = intent.getIntExtra(name, -1); - if (uid >= 0 && UserHandle.getUserId(uid) != userId) { - uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); - intent.putExtra(name, uid); - } + int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + if (uid >= 0 && UserHandle.getUserId(uid) != userId) { + uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); + intent.putExtra(Intent.EXTRA_UID, uid); } if (broadcastAllowList != null && PLATFORM_PACKAGE_NAME.equals(targetPkg)) { intent.putExtra(Intent.EXTRA_VISIBILITY_ALLOW_LIST, diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 2a4882ac7d4f..6103d688e2b1 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -284,6 +284,21 @@ public interface Computer extends PackageDataSnapshot { @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc); + + /** + * Checks if the request is from the system or an app that has the appropriate cross-user + * permissions defined as follows: + * <ul> + * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li> + * <li>INTERACT_ACROSS_USERS if the given {@code userId} is in a different profile group + * to the caller.</li> + * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@code userId} is in the same profile + * group as the caller.</li> + * </ul> + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param message the message to log on security exception + */ @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, boolean requireFullPermission, boolean checkShell, String message); diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 87a83effee1a..664c7bb929d7 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -243,9 +243,7 @@ final class DeletePackageHelper { if (res) { final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0; info.sendPackageRemovedBroadcasts(killApp, removedBySystem); - if (disabledSystemPs != null) { - info.sendSystemPackageUpdatedBroadcasts(disabledSystemPs.getAppId()); - } + info.sendSystemPackageUpdatedBroadcasts(); } // Force a gc to clear up things. @@ -603,9 +601,6 @@ final class DeletePackageHelper { if (outInfo != null) { // Delete the updated package outInfo.mIsRemovedPackageSystemUpdate = true; - if (disabledPs.getAppId() != deletedPs.getAppId()) { - outInfo.mNewAppId = disabledPs.getAppId(); - } } if (disabledPs.getVersionCode() < deletedPs.getVersionCode() @@ -685,7 +680,8 @@ final class DeletePackageHelper { return; } - if (!deleteAllUsers && mPm.getBlockUninstallForUser(internalPackageName, userId)) { + if (!deleteAllUsers && mPm.mIPackageManager + .getBlockUninstallForUser(internalPackageName, userId)) { mPm.mHandler.post(() -> { try { observer.onPackageDeleted(packageName, @@ -705,11 +701,14 @@ final class DeletePackageHelper { // Queue up an async operation since the package deletion may take a little while. mPm.mHandler.post(() -> { int returnCode; - final PackageSetting ps = mPm.mSettings.getPackageLPr(internalPackageName); + final Computer innerSnapshot = mPm.snapshotComputer(); + final PackageStateInternal packageState = + innerSnapshot.getPackageStateInternal(internalPackageName); boolean doDeletePackage = true; - if (ps != null) { + if (packageState != null) { final boolean targetIsInstantApp = - ps.getInstantApp(UserHandle.getUserId(callingUid)); + packageState.getUserStateOrDefault(UserHandle.getUserId(callingUid)) + .isInstantApp(); doDeletePackage = !targetIsInstantApp || canViewInstantApps; } @@ -718,7 +717,7 @@ final class DeletePackageHelper { returnCode = deletePackageX(internalPackageName, versionCode, userId, deleteFlags, false /*removedBySystem*/); } else { - int[] blockUninstallUserIds = getBlockUninstallForUsers( + int[] blockUninstallUserIds = getBlockUninstallForUsers(innerSnapshot, internalPackageName, users); // If nobody is blocking uninstall, proceed with delete for all users if (ArrayUtils.isEmpty(blockUninstallUserIds)) { @@ -769,39 +768,40 @@ final class DeletePackageHelper { } final int callingUserId = UserHandle.getUserId(callingUid); // If the caller installed the pkgName, then allow it to silently uninstall. - if (callingUid == mPm.getPackageUid( - mPm.getInstallerPackageName(pkgName), 0, callingUserId)) { + if (callingUid == mPm.mIPackageManager.getPackageUid( + mPm.mIPackageManager.getInstallerPackageName(pkgName), 0, callingUserId)) { return true; } // Allow package verifier to silently uninstall. - if (mPm.mRequiredVerifierPackage != null && callingUid == mPm.getPackageUid( - mPm.mRequiredVerifierPackage, 0, callingUserId)) { + if (mPm.mRequiredVerifierPackage != null && callingUid == mPm.mIPackageManager + .getPackageUid(mPm.mRequiredVerifierPackage, 0, callingUserId)) { return true; } // Allow package uninstaller to silently uninstall. - if (mPm.mRequiredUninstallerPackage != null && callingUid == mPm.getPackageUid( - mPm.mRequiredUninstallerPackage, 0, callingUserId)) { + if (mPm.mRequiredUninstallerPackage != null && callingUid == mPm.mIPackageManager + .getPackageUid(mPm.mRequiredUninstallerPackage, 0, callingUserId)) { return true; } // Allow storage manager to silently uninstall. - if (mPm.mStorageManagerPackage != null && callingUid == mPm.getPackageUid( + if (mPm.mStorageManagerPackage != null && callingUid == mPm.mIPackageManager.getPackageUid( mPm.mStorageManagerPackage, 0, callingUserId)) { return true; } // Allow caller having MANAGE_PROFILE_AND_DEVICE_OWNERS permission to silently // uninstall for device owner provisioning. - return mPm.checkUidPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, callingUid) + return mPm.mIPackageManager.checkUidPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, callingUid) == PERMISSION_GRANTED; } - private int[] getBlockUninstallForUsers(String packageName, int[] userIds) { + private int[] getBlockUninstallForUsers(@NonNull Computer snapshot, String packageName, + int[] userIds) { int[] result = EMPTY_INT_ARRAY; for (int userId : userIds) { - if (mPm.getBlockUninstallForUser(packageName, userId)) { + if (snapshot.getBlockUninstallForUser(packageName, userId)) { result = ArrayUtils.appendInt(result, userId); } } diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index c8326931f6fd..74ea7cc9b4e1 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -304,7 +304,8 @@ final class DexOptHelper { /*package*/ boolean performDexOpt(DexoptOptions options) { if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; - } else if (mPm.isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) { + } else if (mPm.mIPackageManager.isInstantApp(options.getPackageName(), + UserHandle.getCallingUserId())) { return false; } diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java index 2b46ed404945..05ef3c4ec300 100644 --- a/services/core/java/com/android/server/pm/DumpHelper.java +++ b/services/core/java/com/android/server/pm/DumpHelper.java @@ -288,6 +288,8 @@ final class DumpHelper { ipw.decreaseIndent(); } + final Computer snapshot = mPm.snapshotComputer(); + if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) { final String requiredVerifierPackage = mPm.mRequiredVerifierPackage; @@ -299,14 +301,14 @@ final class DumpHelper { pw.print(" Required: "); pw.print(requiredVerifierPackage); pw.print(" (uid="); - pw.print(mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.USER_SYSTEM)); + pw.print(snapshot.getPackageUid(requiredVerifierPackage, + MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); pw.println(")"); } else if (requiredVerifierPackage != null) { pw.print("vrfy,"); pw.print(requiredVerifierPackage); pw.print(","); - pw.println(mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.USER_SYSTEM)); + pw.println(snapshot.getPackageUid(requiredVerifierPackage, + MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); } } @@ -324,14 +326,14 @@ final class DumpHelper { pw.print(" Using: "); pw.print(verifierPackageName); pw.print(" (uid="); - pw.print(mPm.getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.USER_SYSTEM)); + pw.print(snapshot.getPackageUid(verifierPackageName, + MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); pw.println(")"); } else if (verifierPackageName != null) { pw.print("dv,"); pw.print(verifierPackageName); pw.print(","); - pw.println(mPm.getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.USER_SYSTEM)); + pw.println(snapshot.getPackageUid(verifierPackageName, + MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); } } else { pw.println(); @@ -405,7 +407,7 @@ final class DumpHelper { } if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { - mPm.mComponentResolver.dumpContentProviders(mPm.snapshotComputer(), pw, dumpState, + mPm.mComponentResolver.dumpContentProviders(snapshot, pw, dumpState, packageName); } @@ -661,13 +663,14 @@ final class DumpHelper { final ProtoOutputStream proto = new ProtoOutputStream(fd); synchronized (mPm.mLock) { + final Computer snapshot = mPm.snapshotComputer(); final long requiredVerifierPackageToken = proto.start(PackageServiceDumpProto.REQUIRED_VERIFIER_PACKAGE); proto.write(PackageServiceDumpProto.PackageShortProto.NAME, mPm.mRequiredVerifierPackage); proto.write( PackageServiceDumpProto.PackageShortProto.UID, - mPm.getPackageUid( + snapshot.getPackageUid( mPm.mRequiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); @@ -682,7 +685,7 @@ final class DumpHelper { proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName); proto.write( PackageServiceDumpProto.PackageShortProto.UID, - mPm.getPackageUid( + snapshot.getPackageUid( verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java index c65c2b112706..b4bcd5b3308c 100644 --- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java +++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java @@ -22,11 +22,12 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManagerInternal; import android.os.Process; -import android.os.ServiceManager; import android.util.EventLog; import android.util.Log; +import com.android.server.LocalServices; import com.android.server.pm.dex.DynamicCodeLogger; import libcore.util.HexEncoding; @@ -133,8 +134,7 @@ public class DynamicCodeLoggingService extends JobService { } private static DynamicCodeLogger getDynamicCodeLogger() { - PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); - return pm.getDexManager().getDynamicCodeLogger(); + return LocalServices.getService(PackageManagerInternal.class).getDynamicCodeLogger(); } private class IdleLoggingThread extends Thread { diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 6e4d19f8d277..8667ffd7930c 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -958,10 +958,6 @@ final class InstallPackageHelper { createdAppId.put(packageName, optimisticallyRegisterAppId(result)); versionInfos.put(result.mPkgSetting.getPkg().getPackageName(), mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg())); - if (result.needsNewAppId()) { - request.mInstallResult.mRemovedInfo.mNewAppId = - result.mPkgSetting.getAppId(); - } } catch (PackageManagerException e) { request.mInstallResult.setError("Scanning Failed.", e); return; @@ -2594,8 +2590,6 @@ final class InstallPackageHelper { final int dataLoaderType = installArgs.mDataLoaderType; final boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED; final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null; - final int previousAppId = (res.mRemovedInfo != null && res.mRemovedInfo.mNewAppId >= 0) - ? res.mRemovedInfo.mUid : Process.INVALID_UID; final String packageName = res.mName; final PackageStateInternal pkgSetting = succeeded ? mPm.getPackageStateInternal(packageName) : null; @@ -2703,10 +2697,7 @@ final class InstallPackageHelper { // Send added for users that don't see the package for the first time Bundle extras = new Bundle(); extras.putInt(Intent.EXTRA_UID, res.mUid); - if (previousAppId != Process.INVALID_UID) { - extras.putBoolean(Intent.EXTRA_UID_CHANGING, true); - extras.putInt(Intent.EXTRA_PREVIOUS_UID, previousAppId); - } else if (update) { + if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); @@ -2749,27 +2740,24 @@ final class InstallPackageHelper { // Send replaced for users that don't see the package for the first time if (update) { - // Only send PACKAGE_REPLACED if appId has not changed - if (previousAppId == Process.INVALID_UID) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - packageName, extras, 0 /*flags*/, - null /*targetPackage*/, null /*finishedReceiver*/, - updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList, + mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, + packageName, extras, 0 /*flags*/, + null /*targetPackage*/, null /*finishedReceiver*/, + updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList, + null); + if (installerPackageName != null) { + mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, + extras, 0 /*flags*/, + installerPackageName, null /*finishedReceiver*/, + updateUserIds, instantUserIds, null /*broadcastAllowList*/, + null); + } + if (notifyVerifier) { + mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, + extras, 0 /*flags*/, + mPm.mRequiredVerifierPackage, null /*finishedReceiver*/, + updateUserIds, instantUserIds, null /*broadcastAllowList*/, null); - if (installerPackageName != null) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, - extras, 0 /*flags*/, - installerPackageName, null /*finishedReceiver*/, - updateUserIds, instantUserIds, null /*broadcastAllowList*/, - null); - } - if (notifyVerifier) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, - extras, 0 /*flags*/, - mPm.mRequiredVerifierPackage, null /*finishedReceiver*/, - updateUserIds, instantUserIds, null /*broadcastAllowList*/, - null); - } } mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null /*package*/, null /*extras*/, 0 /*flags*/, @@ -2867,13 +2855,14 @@ final class InstallPackageHelper { VMRuntime.getRuntime().requestConcurrentGC(); } + final Computer snapshot = mPm.snapshotComputer(); // Notify DexManager that the package was installed for new users. // The updated users should already be indexed and the package code paths // should not change. // Don't notify the manager for ephemeral apps as they are not expected to // survive long enough to benefit of background optimizations. for (int userId : firstUserIds) { - PackageInfo info = mPm.getPackageInfo(packageName, /*flags*/ 0, userId); + PackageInfo info = snapshot.getPackageInfo(packageName, /*flags*/ 0, userId); // There's a race currently where some install events may interleave with an // uninstall. This can lead to package info being null (b/36642664). if (info != null) { diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java index 6c80976c0f78..18d2b0c23320 100644 --- a/services/core/java/com/android/server/pm/InstallParams.java +++ b/services/core/java/com/android/server/pm/InstallParams.java @@ -289,8 +289,8 @@ final class InstallParams extends HandlerParams { */ private int fixUpInstallReason(String installerPackageName, int installerUid, int installReason) { - if (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid) - == PERMISSION_GRANTED) { + if (mPm.snapshotComputer().checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, + installerUid) == PERMISSION_GRANTED) { // If the install is being performed by a system app, we trust that app to have set the // install reason correctly. return installReason; diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index e9896617d8e8..5e0fc3bf91e7 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -320,8 +320,12 @@ public class LauncherAppsService extends SystemService { private PackageInstallerService getPackageInstallerService() { if (mPackageInstallerService == null) { - mPackageInstallerService = ((PackageInstallerService) ((PackageManagerService) - ServiceManager.getService("package")).getPackageInstaller()); + try { + mPackageInstallerService = ((PackageInstallerService) ((IPackageManager) + ServiceManager.getService("package")).getPackageInstaller()); + } catch (RemoteException e) { + Slog.wtf(TAG, "Error gettig IPackageInstaller", e); + } } return mPackageInstallerService; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index fe2fe097bdf6..6613f016f66a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -334,7 +334,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements StagingManager.StagedSession stagedSession = session.mStagedSession; if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId() && getSession(stagedSession.getParentSessionId()) == null) { - stagedSession.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, + stagedSession.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "An orphan staged session " + stagedSession.sessionId() + " is found, " + "parent " + stagedSession.getParentSessionId() + " is missing"); continue; @@ -676,7 +676,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements String originatingPackageName = null; if (params.originatingUid != SessionParams.UID_UNKNOWN && params.originatingUid != callingUid) { - String[] packages = mPm.getPackagesForUid(params.originatingUid); + String[] packages = mPm.mIPackageManager.getPackagesForUid(params.originatingUid); if (packages != null && packages.length > 0) { // Choose an arbitrary representative package in the case of a shared UID. originatingPackageName = packages[0]; @@ -727,7 +727,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 && !isCalledBySystemOrShell(callingUid) - && (mPm.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) == 0) { + && (mPm.mIPackageManager.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) + == 0) { throw new SecurityException( "Only system apps could use the PackageManager.INSTALL_INSTANT_APP flag."); } @@ -852,7 +853,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid, null, null, false, false, false, false, null, SessionInfo.INVALID_ID, - false, false, false, SessionInfo.SESSION_NO_ERROR, ""); + false, false, false, PackageManager.INSTALL_UNKNOWN, ""); synchronized (mSessions) { mSessions.put(sessionId, session); @@ -1153,7 +1154,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements .setAdmin(callerPackageName) .write(); } else { - ApplicationInfo appInfo = mPm.getApplicationInfo(callerPackageName, 0, userId); + ApplicationInfo appInfo = mPm.mIPackageManager + .getApplicationInfo(callerPackageName, 0, userId); if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) { mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES, null); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 67d3f9588e3b..35a7eaf29ddc 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -82,7 +82,6 @@ import android.content.pm.InstallationFileParcel; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionInfo.SessionErrorCode; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -462,7 +461,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private boolean mSessionFailed; @GuardedBy("mLock") - private int mSessionErrorCode = SessionInfo.SESSION_NO_ERROR; + private int mSessionErrorCode = PackageManager.INSTALL_UNKNOWN; @GuardedBy("mLock") private String mSessionErrorMessage; @@ -817,25 +816,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // It is safe to access mInstallerUid and mInstallSource without lock // because they are immutable after sealing. + final Computer snapshot = mPm.snapshotComputer(); final boolean isInstallPermissionGranted = - (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, + (snapshot.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); final boolean isSelfUpdatePermissionGranted = - (mPm.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES, + (snapshot.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); final boolean isUpdatePermissionGranted = - (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES, + (snapshot.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); - final boolean isUpdateWithoutUserActionPermissionGranted = (mPm.checkUidPermission( + final boolean isUpdateWithoutUserActionPermissionGranted = (snapshot.checkUidPermission( android.Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION, mInstallerUid) == PackageManager.PERMISSION_GRANTED); - final boolean isInstallDpcPackagesPermissionGranted = (mPm.checkUidPermission( + final boolean isInstallDpcPackagesPermissionGranted = (snapshot.checkUidPermission( android.Manifest.permission.INSTALL_DPC_PACKAGES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); - final int targetPackageUid = mPm.getPackageUid(packageName, 0, userId); + final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId); final boolean isUpdate = targetPackageUid != -1 || isApexSession(); final InstallSourceInfo existingInstallSourceInfo = isUpdate - ? mPm.getInstallSourceInfo(packageName) + ? snapshot.getInstallSourceInfo(packageName) : null; final String existingInstallerPackageName = existingInstallSourceInfo != null ? existingInstallSourceInfo.getInstallingPackageName() @@ -2009,12 +2009,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void transfer(String packageName) { Preconditions.checkArgument(!TextUtils.isEmpty(packageName)); - ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId); + final Computer snapshot = mPm.snapshotComputer(); + ApplicationInfo newOwnerAppInfo = snapshot.getApplicationInfo(packageName, 0, userId); if (newOwnerAppInfo == null) { throw new ParcelableException(new PackageManager.NameNotFoundException(packageName)); } - if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission( + if (PackageManager.PERMISSION_GRANTED != snapshot.checkUidPermission( Manifest.permission.INSTALL_PACKAGES, newOwnerAppInfo.uid)) { throw new SecurityException("Destination package " + packageName + " does not have " + "the " + Manifest.permission.INSTALL_PACKAGES + " permission"); @@ -2329,7 +2330,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } else { PackageManagerException e = (PackageManagerException) t.getCause(); - setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, + setSessionFailed(e.error, PackageManager.installStatusToString(e.error, e.getMessage())); dispatchSessionFinished(e.error, e.getMessage(), null); maybeFinishChildSessions(e.error, e.getMessage()); @@ -2509,7 +2510,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Package didn't install; no valid uid packageUid = Process.INVALID_UID; } else { - packageUid = mPm.getPackageUid(packageName, 0, userId); + packageUid = mPm.snapshotComputer().getPackageUid(packageName, 0, userId); } FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLER_V2_REPORTED, isIncrementalInstallation(), @@ -2667,7 +2668,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mResolvedStagedFiles.clear(); mResolvedInheritedFiles.clear(); - final PackageInfo pkgInfo = mPm.getPackageInfo( + final PackageInfo pkgInfo = mPm.snapshotComputer().getPackageInfo( params.appPackageName, PackageManager.GET_SIGNATURES | PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES /*flags*/, userId); @@ -3786,8 +3787,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { }; try { - final PackageInfo pkgInfo = mPm.getPackageInfo(this.params.appPackageName, 0, - userId); + final PackageInfo pkgInfo = mPm.snapshotComputer() + .getPackageInfo(this.params.appPackageName, 0, userId); final File inheritedDir = (pkgInfo != null && pkgInfo.applicationInfo != null) ? new File( pkgInfo.applicationInfo.getCodePath()).getParentFile() : null; @@ -4043,7 +4044,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSessionReady = true; mSessionApplied = false; mSessionFailed = false; - mSessionErrorCode = SessionInfo.SESSION_NO_ERROR; + mSessionErrorCode = PackageManager.INSTALL_UNKNOWN; mSessionErrorMessage = ""; } mCallback.onSessionChanged(this); @@ -4071,7 +4072,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSessionReady = false; mSessionApplied = true; mSessionFailed = false; - mSessionErrorCode = SessionInfo.SESSION_NO_ERROR; + mSessionErrorCode = INSTALL_SUCCEEDED; mSessionErrorMessage = ""; Slog.d(TAG, "Marking session " + sessionId + " as applied"); } @@ -4101,7 +4102,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } /** {@hide} */ - @SessionErrorCode int getSessionErrorCode() { synchronized (mLock) { return mSessionErrorCode; @@ -4532,8 +4532,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME); final String installerAttributionTag = readStringAttribute(in, ATTR_INSTALLER_ATTRIBUTION_TAG); - final int installerUid = in.getAttributeInt(null, ATTR_INSTALLER_UID, pm.getPackageUid( - installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)); + final int installerUid = in.getAttributeInt(null, ATTR_INSTALLER_UID, pm.snapshotComputer() + .getPackageUid(installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, + userId)); final String installInitiatingPackageName = readStringAttribute(in, ATTR_INITIATING_PACKAGE_NAME); final String installOriginatingPackageName = @@ -4589,7 +4590,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isFailed = in.getAttributeBoolean(null, ATTR_IS_FAILED, false); final boolean isApplied = in.getAttributeBoolean(null, ATTR_IS_APPLIED, false); final int sessionErrorCode = in.getAttributeInt(null, ATTR_SESSION_ERROR_CODE, - SessionInfo.SESSION_NO_ERROR); + PackageManager.INSTALL_UNKNOWN); final String sessionErrorMessage = readStringAttribute(in, ATTR_SESSION_ERROR_MESSAGE); if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { diff --git a/services/core/java/com/android/server/pm/PackageManagerNative.java b/services/core/java/com/android/server/pm/PackageManagerNative.java index 37daf112aa69..9a43008acfdf 100644 --- a/services/core/java/com/android/server/pm/PackageManagerNative.java +++ b/services/core/java/com/android/server/pm/PackageManagerNative.java @@ -71,7 +71,7 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { @Override public String[] getAllPackages() { - return mPm.getAllPackages().toArray(new String[0]); + return mPm.snapshotComputer().getAllPackages().toArray(new String[0]); } @Override @@ -82,7 +82,7 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { if (uids == null || uids.length == 0) { return null; } - names = mPm.getNamesForUids(uids); + names = mPm.snapshotComputer().getNamesForUids(uids); results = (names != null) ? names : new String[uids.length]; // massage results so they can be parsed by the native binder for (int i = results.length - 1; i >= 0; --i) { @@ -104,13 +104,14 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { // NB: this differentiates between preloads and sideloads @Override public String getInstallerForPackage(String packageName) throws RemoteException { - final String installerName = mPm.getInstallerPackageName(packageName); + final Computer snapshot = mPm.snapshotComputer(); + final String installerName = snapshot.getInstallerPackageName(packageName); if (!TextUtils.isEmpty(installerName)) { return installerName; } // differentiate between preload and sideload int callingUser = UserHandle.getUserId(Binder.getCallingUid()); - ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, + ApplicationInfo appInfo = snapshot.getApplicationInfo(packageName, /*flags*/ 0, /*userId*/ callingUser); if (appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { @@ -123,7 +124,8 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { public long getVersionCodeForPackage(String packageName) throws RemoteException { try { int callingUser = UserHandle.getUserId(Binder.getCallingUid()); - PackageInfo pInfo = mPm.getPackageInfo(packageName, 0, callingUser); + PackageInfo pInfo = mPm.snapshotComputer() + .getPackageInfo(packageName, 0, callingUser); if (pInfo != null) { return pInfo.getLongVersionCode(); } @@ -134,7 +136,7 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { @Override public int getTargetSdkVersionForPackage(String packageName) throws RemoteException { - int targetSdk = mPm.getTargetSdkVersion(packageName); + int targetSdk = mPm.snapshotComputer().getTargetSdkVersion(packageName); if (targetSdk != -1) { return targetSdk; } @@ -145,7 +147,8 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { @Override public boolean isPackageDebuggable(String packageName) throws RemoteException { int callingUser = UserHandle.getCallingUserId(); - ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0, callingUser); + ApplicationInfo appInfo = mPm.snapshotComputer() + .getApplicationInfo(packageName, 0, callingUser); if (appInfo != null) { return (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE)); } @@ -157,9 +160,10 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { public boolean[] isAudioPlaybackCaptureAllowed(String[] packageNames) throws RemoteException { int callingUser = UserHandle.getUserId(Binder.getCallingUid()); + final Computer snapshot = mPm.snapshotComputer(); boolean[] results = new boolean[packageNames.length]; for (int i = results.length - 1; i >= 0; --i) { - ApplicationInfo appInfo = mPm.getApplicationInfo(packageNames[i], 0, callingUser); + ApplicationInfo appInfo = snapshot.getApplicationInfo(packageNames[i], 0, callingUser); results[i] = appInfo != null && appInfo.isAudioPlaybackCaptureAllowed(); } return results; @@ -168,7 +172,7 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { @Override public int getLocationFlags(String packageName) throws RemoteException { int callingUser = UserHandle.getUserId(Binder.getCallingUid()); - ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, + ApplicationInfo appInfo = mPm.snapshotComputer().getApplicationInfo(packageName, /*flags*/ 0, /*userId*/ callingUser); if (appInfo == null) { @@ -188,7 +192,8 @@ final class PackageManagerNative extends IPackageManagerNative.Stub { @Override public boolean hasSha256SigningCertificate(String packageName, byte[] certificate) throws RemoteException { - return mPm.hasSigningCertificate(packageName, certificate, CERT_INPUT_SHA256); + return mPm.snapshotComputer() + .hasSigningCertificate(packageName, certificate, CERT_INPUT_SHA256); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a70cff98ed93..e20a861e2eae 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -105,11 +105,6 @@ import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ComponentEnabledSetting; -import android.content.pm.PackageManager.ComponentType; -import android.content.pm.PackageManager.LegacyPackageDeleteObserver; -import android.content.pm.PackageManager.ModuleInfoFlags; -import android.content.pm.PackageManager.Property; -import android.content.pm.PackageManager.PropertyLocation; import android.content.pm.PackageManagerInternal; import android.content.pm.PackagePartitions; import android.content.pm.ParceledListSlice; @@ -218,6 +213,7 @@ import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.ArtUtils; import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.DynamicCodeLogger; import com.android.server.pm.dex.ViewCompiler; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; @@ -334,8 +330,8 @@ import java.util.function.Consumer; * $ cts-tradefed run commandAndExit cts -m CtsAppSecurityHostTestCases * </pre> */ -public class PackageManagerService extends IPackageManager.Stub - implements PackageSender, TestUtilityService { +public class PackageManagerService implements PackageSender, TestUtilityService { + static final String TAG = "PackageManager"; public static final boolean DEBUG_SETTINGS = false; static final boolean DEBUG_PREFERRED = false; @@ -648,6 +644,8 @@ public class PackageManagerService extends IPackageManager.Stub */ boolean mPromoteSystemApps; + // TODO: Make IPackageManager reference private to hide discouraged APIs + final IPackageManagerImpl mIPackageManager; private final PackageManagerInternal mPmInternal; private final TestUtilityService mTestUtilityService; @@ -1060,7 +1058,7 @@ public class PackageManagerService extends IPackageManager.Stub private volatile Computer mSnapshotComputer; // A trampoline that directs callers to either the live or snapshot computer. - private final ComputerTracker mComputer = new ComputerTracker(this); + final ComputerTracker mComputer = new ComputerTracker(this); // If true, the snapshot is invalid (stale). The attribute is static since it may be // set from outside classes. The attribute may be set to true anywhere, although it @@ -1168,15 +1166,6 @@ public class PackageManagerService extends IPackageManager.Stub onChange(null); } - @Override - public void notifyPackagesReplacedReceived(String[] packages) { - Computer computer = snapshotComputer(); - ArraySet<String> packagesToNotify = computer.getNotifyPackagesForReplacedReceived(packages); - for (int index = 0; index < packagesToNotify.size(); index++) { - notifyInstallObserver(packagesToNotify.valueAt(index), false /* killApp */); - } - } - void notifyInstallObserver(String packageName, boolean killApp) { final Pair<PackageInstalledInfo, IPackageInstallObserver2> pair = killApp ? mPendingKillInstallObservers.remove(packageName) @@ -1233,16 +1222,6 @@ public class PackageManagerService extends IPackageManager.Stub PRUNE_UNUSED_SHARED_LIBRARIES_DELAY); } - @Override - public void requestPackageChecksums(@NonNull String packageName, boolean includeSplits, - @Checksum.TypeMask int optional, @Checksum.TypeMask int required, - @Nullable List trustedInstallers, - @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId) { - requestChecksumsInternal(packageName, includeSplits, optional, required, trustedInstallers, - onChecksumsReadyListener, userId, mInjector.getBackgroundExecutor(), - mInjector.getBackgroundHandler()); - } - /** * Requests checksums for the APK file. * See {@link PackageInstaller.Session#requestChecksums} for details. @@ -1290,7 +1269,8 @@ public class PackageManagerService extends IPackageManager.Stub if (applicationInfo == null) { throw new ParcelableException(new PackageManager.NameNotFoundException(packageName)); } - final InstallSourceInfo installSourceInfo = getInstallSourceInfo(packageName); + final InstallSourceInfo installSourceInfo = + mIPackageManager.getInstallSourceInfo(packageName); final String installerPackageName = installSourceInfo != null ? installSourceInfo.getInitiatingPackageName() : null; @@ -1432,9 +1412,9 @@ public class PackageManagerService extends IPackageManager.Stub } } - public static PackageManagerService main(Context context, Installer installer, - @NonNull DomainVerificationService domainVerificationService, boolean factoryTest, - boolean onlyCore) { + public static Pair<PackageManagerService, IPackageManager> main(Context context, + Installer installer, @NonNull DomainVerificationService domainVerificationService, + boolean factoryTest, boolean onlyCore) { // Self-check for initial settings. PackageManagerServiceCompilerMapping.checkProperties(); final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", @@ -1465,10 +1445,10 @@ public class PackageManagerService extends IPackageManager.Stub (i, pm) -> SystemConfig.getInstance(), (i, pm) -> new PackageDexOptimizer(i.getInstaller(), i.getInstallLock(), i.getContext(), "*dexopt*"), - (i, pm) -> new DexManager(i.getContext(), pm, i.getPackageDexOptimizer(), + (i, pm) -> new DexManager(i.getContext(), pm.mIPackageManager, + i.getPackageDexOptimizer(), i.getInstaller(), i.getInstallLock()), + (i, pm) -> new ArtManagerService(i.getContext(), pm.mIPackageManager, i.getInstaller(), i.getInstallLock()), - (i, pm) -> new ArtManagerService(i.getContext(), pm, i.getInstaller(), - i.getInstallLock()), (i, pm) -> ApexManager.getInstance(), (i, pm) -> new ViewCompiler(i.getInstallLock(), i.getInstaller()), (i, pm) -> (IncrementalManager) @@ -1490,7 +1470,7 @@ public class PackageManagerService extends IPackageManager.Stub i.getContext(), pm, i::getScanningPackageParser), (i, pm, cn) -> new InstantAppResolverConnection( i.getContext(), cn, Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE), - (i, pm) -> new ModuleInfoProvider(i.getContext(), pm), + (i, pm) -> new ModuleInfoProvider(i.getContext(), pm.mIPackageManager), (i, pm) -> LegacyPermissionManagerService.create(i.getContext()), (i, pm) -> domainVerificationService, (i, pm) -> { @@ -1551,11 +1531,11 @@ public class PackageManagerService extends IPackageManager.Stub selinuxChangeListener); m.installAllowlistedSystemPackages(); - ServiceManager.addService("package", m); + ServiceManager.addService("package", m.mIPackageManager); final PackageManagerNative pmn = new PackageManagerNative(m); ServiceManager.addService("package_native", pmn); LocalManagerRegistry.addManager(PackageManagerLocal.class, m.new PackageManagerLocalImpl()); - return m; + return Pair.create(m, m.mIPackageManager); } /** Install/uninstall system packages for all users based on their user-type, as applicable. */ @@ -1661,6 +1641,7 @@ public class PackageManagerService extends IPackageManager.Stub mPackageDexOptimizer = testParams.packageDexOptimizer; mPackageParserCallback = testParams.packageParserCallback; mPendingBroadcasts = testParams.pendingPackageBroadcasts; + mIPackageManager = new IPackageManagerImpl(); mPmInternal = testParams.pmInternal; mTestUtilityService = testParams.testUtilityService; mProcessLoggingHandler = testParams.processLoggingHandler; @@ -1722,6 +1703,7 @@ public class PackageManagerService extends IPackageManager.Stub public PackageManagerService(PackageManagerServiceInjector injector, boolean onlyCore, boolean factoryTest, final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) { + mIPackageManager = new IPackageManagerImpl(); mIsEngBuild = isEngBuild; mIsUserDebugBuild = isUserDebugBuild; mSdkVersion = sdkVersion; @@ -1773,7 +1755,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean hasFeature(String feature) { - return PackageManagerService.this.hasSystemFeature(feature, 0); + return PackageManagerService.this.mIPackageManager.hasSystemFeature(feature, 0); } }; @@ -2000,11 +1982,13 @@ public class PackageManagerService extends IPackageManager.Stub mSetupWizardPackage = getSetupWizardPackageNameImpl(computer); mComponentResolver.fixProtectedFilterPriorities(mPmInternal.getSetupWizardPackageName()); - mDefaultTextClassifierPackage = getDefaultTextClassifierPackageName(); - mSystemTextClassifierPackageName = getSystemTextClassifierPackageName(); + mDefaultTextClassifierPackage = mIPackageManager.getDefaultTextClassifierPackageName(); + mSystemTextClassifierPackageName = + mIPackageManager.getSystemTextClassifierPackageName(); mConfiguratorPackage = getDeviceConfiguratorPackageName(); - mAppPredictionServicePackage = getAppPredictionServicePackageName(); - mIncidentReportApproverPackage = getIncidentReportApproverPackageName(); + mAppPredictionServicePackage = mIPackageManager.getAppPredictionServicePackageName(); + mIncidentReportApproverPackage = + mIPackageManager.getIncidentReportApproverPackageName(); mRetailDemoPackage = getRetailDemoPackageName(); mOverlayConfigSignaturePackage = getOverlayConfigSignaturePackageName(); mRecentsPackage = getRecentsPackageName(); @@ -2159,7 +2143,7 @@ public class PackageManagerService extends IPackageManager.Stub mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr(computer); mSettings.setPermissionControllerVersion( - getPackageInfo(mRequiredPermissionControllerPackage, 0, + mIPackageManager.getPackageInfo(mRequiredPermissionControllerPackage, 0, UserHandle.USER_SYSTEM).getLongVersionCode()); // Resolve the sdk sandbox package @@ -2207,7 +2191,8 @@ public class PackageManagerService extends IPackageManager.Stub // scanning). final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>(); for (int userId : userIds) { - userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList()); + userPackages.put(userId, mIPackageManager.getInstalledPackages(/*flags*/ 0, userId) + .getList()); } mDexManager.load(userPackages); if (mIsUpgrade) { @@ -2260,19 +2245,16 @@ public class PackageManagerService extends IPackageManager.Stub setUpInstantAppInstallerActivityLP(getInstantAppInstallerLPr()); } - @Override public boolean isFirstBoot() { // allow instant applications return mFirstBoot; } - @Override public boolean isOnlyCoreApps() { // allow instant applications return mOnlyCore; } - @Override public boolean isDeviceUpgrading() { // allow instant applications // The system property allows testing ota flow when upgraded to the same image. @@ -2393,8 +2375,9 @@ public class PackageManagerService extends IPackageManager.Stub for (int i = 0; i < N; i++) { final ResolveInfo cur = matches.get(i); final String packageName = cur.getComponentInfo().packageName; - if (checkPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT, - packageName, UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) { + if (mIPackageManager.checkPermission( + android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT, packageName, + UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) { continue; } @@ -2422,8 +2405,9 @@ public class PackageManagerService extends IPackageManager.Stub for (int i = 0; i < N; i++) { final ResolveInfo cur = matches.get(i); final String packageName = cur.getComponentInfo().packageName; - if (checkPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, - packageName, UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) { + if (mIPackageManager.checkPermission( + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, packageName, + UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Domain verification agent found but does not hold permission: " + packageName); continue; @@ -2446,14 +2430,6 @@ public class PackageManagerService extends IPackageManager.Stub return null; } - @Override - public @Nullable ComponentName getInstantAppResolverComponent() { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return null; - } - return getInstantAppResolver(); - } - private @Nullable ComponentName getInstantAppResolver() { final String[] packageArray = mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage); @@ -2543,7 +2519,7 @@ public class PackageManagerService extends IPackageManager.Stub Iterator<ResolveInfo> iter = matches.iterator(); while (iter.hasNext()) { final ResolveInfo rInfo = iter.next(); - if (checkPermission( + if (mIPackageManager.checkPermission( Manifest.permission.INSTALL_PACKAGES, rInfo.activityInfo.packageName, 0) == PERMISSION_GRANTED || mIsEngBuild) { continue; @@ -2574,18 +2550,18 @@ public class PackageManagerService extends IPackageManager.Stub return matches.get(0).getComponentInfo().getComponentName(); } - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - try { - return super.onTransact(code, data, reply, flags); - } catch (RuntimeException e) { - if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException) - && !(e instanceof ParcelableException)) { - Slog.wtf(TAG, "Package Manager Unexpected Exception", e); - } - throw e; - } + /** + * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int) + */ + boolean shouldFilterApplication( + @Nullable PackageStateInternal ps, int callingUid, int userId) { + return mComputer.shouldFilterApplication( + ps, callingUid, userId); + } + + private @PackageStartability int getPackageStartability(String packageName, + int callingUid, int userId) { + return mComputer.getPackageStartability(mSafeMode, packageName, callingUid, userId); } /** @@ -2611,118 +2587,11 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.generatePackageInfo(ps, flags, userId); } - @Override - public void checkPackageStartable(String packageName, int userId) { - final int callingUid = Binder.getCallingUid(); - if (getInstantAppPackageName(callingUid) != null) { - throw new SecurityException("Instant applications don't have access to this method"); - } - if (!mUserManager.exists(userId)) { - throw new SecurityException("User doesn't exist"); - } - enforceCrossUserPermission(callingUid, userId, false, false, "checkPackageStartable"); - switch (getPackageStartability(packageName, callingUid, userId)) { - case PACKAGE_STARTABILITY_NOT_FOUND: - throw new SecurityException("Package " + packageName + " was not found!"); - case PACKAGE_STARTABILITY_NOT_SYSTEM: - throw new SecurityException("Package " + packageName + " not a system app!"); - case PACKAGE_STARTABILITY_FROZEN: - throw new SecurityException("Package " + packageName + " is currently frozen!"); - case PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED: - throw new SecurityException("Package " + packageName + " is not encryption aware!"); - case PACKAGE_STARTABILITY_OK: - default: - } - } - - private @PackageStartability int getPackageStartability(String packageName, - int callingUid, int userId) { - return mComputer.getPackageStartability(mSafeMode, packageName, callingUid, userId); - } - - @Override - public boolean isPackageAvailable(String packageName, int userId) { - return mComputer.isPackageAvailable(packageName, userId); - } - - @Override - public PackageInfo getPackageInfo(String packageName, - @PackageManager.PackageInfoFlagsBits long flags, int userId) { - return mComputer.getPackageInfo(packageName, flags, userId); - } - - @Override - public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, - @PackageManager.PackageInfoFlagsBits long flags, int userId) { - return mComputer.getPackageInfoInternal(versionedPackage.getPackageName(), - versionedPackage.getLongVersionCode(), flags, Binder.getCallingUid(), userId); - } - - /** - * Returns whether or not access to the application should be filtered. - * <p> - * Access may be limited based upon whether the calling or target applications - * are instant applications. - * - * @see #canViewInstantApps(int, int) - */ - private boolean shouldFilterApplication(@Nullable PackageStateInternal ps, int callingUid, - @Nullable ComponentName component, @ComponentType int componentType, int userId) { - return mComputer.shouldFilterApplication(ps, callingUid, - component, componentType, userId); - } - - /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int) - */ - boolean shouldFilterApplication( - @Nullable PackageStateInternal ps, int callingUid, int userId) { - return mComputer.shouldFilterApplication( - ps, callingUid, userId); - } - - /** - * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int) - */ - private boolean shouldFilterApplication(@NonNull SharedUserSetting sus, int callingUid, - int userId) { - return mComputer.shouldFilterApplication(sus, callingUid, userId); - } - - private boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid, - int userId, @PackageManager.ComponentInfoFlagsBits long flags) { - return mComputer.filterSharedLibPackage(ps, uid, userId, flags); - } - - @Override - public String[] currentToCanonicalPackageNames(String[] names) { - return mComputer.currentToCanonicalPackageNames(names); - } - - @Override - public String[] canonicalToCurrentPackageNames(String[] names) { - return mComputer.canonicalToCurrentPackageNames(names); - } - - @Override - public int getPackageUid(@NonNull String packageName, - @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) { - return mComputer.getPackageUid(packageName, flags, userId); - } - int getPackageUidInternal(String packageName, @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) { return mComputer.getPackageUidInternal(packageName, flags, userId, callingUid); } - @Override - public int[] getPackageGids(String packageName, @PackageManager.PackageInfoFlagsBits long flags, - int userId) { - return mComputer.getPackageGids(packageName, flags, userId); - } - - // NOTE: Can't remove due to unsupported app usage - @Override public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) { // Because this is accessed via the package manager service AIDL, // go through the permission manager service AIDL @@ -2730,18 +2599,6 @@ public class PackageManagerService extends IPackageManager.Stub .getPermissionGroupInfo(groupName, flags); } - private ApplicationInfo generateApplicationInfoFromSettings(String packageName, - @PackageManager.ApplicationInfoFlagsBits long flags, int filterCallingUid, int userId) { - return mComputer.generateApplicationInfoFromSettings(packageName, flags, filterCallingUid, - userId); - } - - @Override - public ApplicationInfo getApplicationInfo(String packageName, - @PackageManager.ApplicationInfoFlagsBits long flags, int userId) { - return mComputer.getApplicationInfo(packageName, flags, userId); - } - /** * Important: The provided filterCallingUid is used exclusively to filter out applications * that can be seen based on user state. It's typically the original caller uid prior @@ -2755,61 +2612,6 @@ public class PackageManagerService extends IPackageManager.Stub filterCallingUid, userId); } - @Override - public void deletePreloadsFileCache() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CLEAR_APP_CACHE, - "deletePreloadsFileCache"); - File dir = Environment.getDataPreloadsFileCacheDirectory(); - Slog.i(TAG, "Deleting preloaded file cache " + dir); - FileUtils.deleteContents(dir); - } - - @Override - public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize, - final @StorageManager.AllocateFlags int flags, final IPackageDataObserver observer) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CLEAR_APP_CACHE, null); - mHandler.post(() -> { - boolean success = false; - try { - freeStorage(volumeUuid, freeStorageSize, flags); - success = true; - } catch (IOException e) { - Slog.w(TAG, e); - } - if (observer != null) { - try { - observer.onRemoveCompleted(null, success); - } catch (RemoteException e) { - Slog.w(TAG, e); - } - } - }); - } - - @Override - public void freeStorage(final String volumeUuid, final long freeStorageSize, - final @StorageManager.AllocateFlags int flags, final IntentSender pi) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CLEAR_APP_CACHE, TAG); - mHandler.post(() -> { - boolean success = false; - try { - freeStorage(volumeUuid, freeStorageSize, flags); - success = true; - } catch (IOException e) { - Slog.w(TAG, e); - } - if (pi != null) { - try { - pi.sendIntent(null, success ? 1 : 0, null, null, null); - } catch (SendIntentException e) { - Slog.w(TAG, e); - } - } - }); - } - /** * Blocking call to clear all cached app data above quota. */ @@ -2846,7 +2648,7 @@ public class PackageManagerService extends IPackageManager.Stub // 2. Consider preloaded data (after 1w honeymoon, unless aggressive) if (internalVolume && (aggressive || SystemProperties .getBoolean("persist.sys.preloads.file_cache_expired", false))) { - deletePreloadsFileCache(); + mIPackageManager.deletePreloadsFileCache(); if (file.getUsableSpace() >= bytes) return; } @@ -2985,17 +2787,6 @@ public class PackageManagerService extends IPackageManager.Stub wantInstantApps, isImplicitImageCaptureIntentAndNotSetByDpc); } - @Override - public int getTargetSdkVersion(@NonNull String packageName) { - return mComputer.getTargetSdkVersion(packageName); - } - - @Override - public ActivityInfo getActivityInfo(ComponentName component, - @PackageManager.ComponentInfoFlagsBits long flags, int userId) { - return mComputer.getActivityInfo(component, flags, userId); - } - /** * Important: The provided filterCallingUid is used exclusively to filter out activities * that can be seen based on user state. It's typically the original caller uid prior @@ -3008,33 +2799,6 @@ public class PackageManagerService extends IPackageManager.Stub filterCallingUid, userId); } - @Override - public boolean activitySupportsIntent(ComponentName component, Intent intent, - String resolvedType) { - return mComputer.activitySupportsIntent(mResolveComponentName, component, intent, - resolvedType); - } - - @Override - public ActivityInfo getReceiverInfo(ComponentName component, - @PackageManager.ComponentInfoFlagsBits long flags, int userId) { - return mComputer.getReceiverInfo(component, flags, userId); - } - - @Override - public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(String packageName, - @PackageManager.PackageInfoFlagsBits long flags, int userId) { - return mComputer.getSharedLibraries(packageName, flags, userId); - } - - @Nullable - @Override - public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( - @NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags, - @NonNull int userId) { - return mComputer.getDeclaredSharedLibraries(packageName, flags, userId); - } - @Nullable List<VersionedPackage> getPackagesUsingSharedLibrary( SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags, @@ -3042,95 +2806,15 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId); } - @Nullable - @Override - public ServiceInfo getServiceInfo(@NonNull ComponentName component, - @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) { - return mComputer.getServiceInfo(component, flags, userId); - } - - @Nullable - @Override - public ProviderInfo getProviderInfo(@NonNull ComponentName component, - @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) { - return mComputer.getProviderInfo(component, flags, userId); - } - - @Override - public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags) { + public ModuleInfo getModuleInfo(String packageName, @PackageManager.ModuleInfoFlags int flags) { return mModuleInfoProvider.getModuleInfo(packageName, flags); } - @Override - public List<ModuleInfo> getInstalledModules(int flags) { - return mModuleInfoProvider.getInstalledModules(flags); - } - - @Nullable - @Override - public String[] getSystemSharedLibraryNames() { - return mComputer.getSystemSharedLibraryNames(); - } - - @Override - public @NonNull String getServicesSystemSharedLibraryPackageName() { - return mServicesExtensionPackageName; - } - - @Override - public @NonNull String getSharedSystemSharedLibraryPackageName() { - return mSharedSystemSharedLibraryPackageName; - } - @GuardedBy("mLock") void updateSequenceNumberLP(PackageSetting pkgSetting, int[] userList) { mChangedPackagesTracker.updateSequenceNumber(pkgSetting.getPackageName(), userList); } - @Override - public ChangedPackages getChangedPackages(int sequenceNumber, int userId) { - final int callingUid = Binder.getCallingUid(); - if (getInstantAppPackageName(callingUid) != null) { - return null; - } - if (!mUserManager.exists(userId)) { - return null; - } - enforceCrossUserPermission(callingUid, userId, false, false, "getChangedPackages"); - final ChangedPackages changedPackages = mChangedPackagesTracker.getChangedPackages( - sequenceNumber, userId); - - if (changedPackages != null) { - final List<String> packageNames = changedPackages.getPackageNames(); - for (int index = packageNames.size() - 1; index >= 0; index--) { - // Filter out the changes if the calling package should not be able to see it. - final PackageSetting ps = mSettings.getPackageLPr(packageNames.get(index)); - if (shouldFilterApplication(ps, callingUid, userId)) { - packageNames.remove(index); - } - } - } - - return changedPackages; - } - - @Override - public @NonNull ParceledListSlice<FeatureInfo> getSystemAvailableFeatures() { - // allow instant applications - ArrayList<FeatureInfo> res; - synchronized (mAvailableFeatures) { - res = new ArrayList<>(mAvailableFeatures.size() + 1); - res.addAll(mAvailableFeatures.values()); - } - final FeatureInfo fi = new FeatureInfo(); - fi.reqGlEsVersion = SystemProperties.getInt("ro.opengles.version", - FeatureInfo.GL_ES_VERSION_UNDEFINED); - res.add(fi); - - return new ParceledListSlice<>(res); - } - - @Override public boolean hasSystemFeature(String name, int version) { // allow instant applications synchronized (mAvailableFeatures) { @@ -3144,29 +2828,10 @@ public class PackageManagerService extends IPackageManager.Stub } // NOTE: Can't remove due to unsupported app usage - @Override public int checkPermission(String permName, String pkgName, int userId) { return mPermissionManager.checkPermission(pkgName, permName, userId); } - // NOTE: Can't remove without a major refactor. Keep around for now. - @Override - public int checkUidPermission(String permName, int uid) { - return mComputer.checkUidPermission(permName, uid); - } - - @Override - public String getPermissionControllerPackageName() { - final int callingUid = Binder.getCallingUid(); - if (mComputer.getPackageStateFiltered(mRequiredPermissionControllerPackage, - callingUid, UserHandle.getUserId(callingUid)) != null) { - return mRequiredPermissionControllerPackage; - } - - throw new IllegalStateException("PermissionController is not found"); - } - - @Override public String getSdkSandboxPackageName() { return mRequiredSdkSandboxPackage; } @@ -3175,171 +2840,6 @@ public class PackageManagerService extends IPackageManager.Stub return mRequiredInstallerPackage; } - // NOTE: Can't remove due to unsupported app usage - @Override - public boolean addPermission(PermissionInfo info) { - // Because this is accessed via the package manager service AIDL, - // go through the permission manager service AIDL - return mContext.getSystemService(PermissionManager.class).addPermission(info, false); - } - - // NOTE: Can't remove due to unsupported app usage - @Override - public boolean addPermissionAsync(PermissionInfo info) { - // Because this is accessed via the package manager service AIDL, - // go through the permission manager service AIDL - return mContext.getSystemService(PermissionManager.class).addPermission(info, true); - } - - // NOTE: Can't remove due to unsupported app usage - @Override - public void removePermission(String permName) { - // Because this is accessed via the package manager service AIDL, - // go through the permission manager service AIDL - mContext.getSystemService(PermissionManager.class).removePermission(permName); - } - - // NOTE: Can't remove due to unsupported app usage - @Override - public void grantRuntimePermission(String packageName, String permName, final int userId) { - // Because this is accessed via the package manager service AIDL, - // go through the permission manager service AIDL - mContext.getSystemService(PermissionManager.class) - .grantRuntimePermission(packageName, permName, UserHandle.of(userId)); - } - - @Override - public boolean isProtectedBroadcast(String actionName) { - if (actionName != null) { - // TODO: remove these terrible hacks - if (actionName.startsWith("android.net.netmon.lingerExpired") - || actionName.startsWith("com.android.server.sip.SipWakeupTimer") - || actionName.startsWith("com.android.internal.telephony.data-reconnect") - || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) { - return true; - } - } - // allow instant applications - synchronized (mProtectedBroadcasts) { - return mProtectedBroadcasts.contains(actionName); - } - } - - @Override - public int checkSignatures(@NonNull String pkg1, @NonNull String pkg2) { - return mComputer.checkSignatures(pkg1, pkg2); - } - - @Override - public int checkUidSignatures(int uid1, int uid2) { - return mComputer.checkUidSignatures(uid1, uid2); - } - - @Override - public boolean hasSigningCertificate(@NonNull String packageName, @NonNull byte[] certificate, - @PackageManager.CertificateInputType int type) { - return mComputer.hasSigningCertificate(packageName, certificate, type); - } - - @Override - public boolean hasUidSigningCertificate(int uid, @NonNull byte[] certificate, - @PackageManager.CertificateInputType int type) { - return mComputer.hasUidSigningCertificate(uid, certificate, type); - } - - @Override - public List<String> getAllPackages() { - return mComputer.getAllPackages(); - } - - /** - * <em>IMPORTANT:</em> Not all packages returned by this method may be known - * to the system. There are two conditions in which this may occur: - * <ol> - * <li>The package is on adoptable storage and the device has been removed</li> - * <li>The package is being removed and the internal structures are partially updated</li> - * </ol> - * The second is an artifact of the current data structures and should be fixed. See - * b/111075456 for one such instance. - * This binder API is cached. If the algorithm in this method changes, - * or if the underlying objecs (as returned by getSettingLPr()) change - * then the logic that invalidates the cache must be revisited. See - * calls to invalidateGetPackagesForUidCache() to locate the points at - * which the cache is invalidated. - */ - @Override - public String[] getPackagesForUid(int uid) { - final int callingUid = Binder.getCallingUid(); - final int userId = UserHandle.getUserId(uid); - enforceCrossUserOrProfilePermission(callingUid, userId, - /* requireFullPermission */ false, - /* checkShell */ false, "getPackagesForUid"); - return mComputer.getPackagesForUid(uid); - } - - @Nullable - @Override - public String getNameForUid(int uid) { - return mComputer.getNameForUid(uid); - } - - @Nullable - @Override - public String[] getNamesForUids(@NonNull int[] uids) { - return mComputer.getNamesForUids(uids); - } - - @Override - public int getUidForSharedUser(@NonNull String sharedUserName) { - return mComputer.getUidForSharedUser(sharedUserName); - } - - @Override - public int getFlagsForUid(int uid) { - return mComputer.getFlagsForUid(uid); - } - - @Override - public int getPrivateFlagsForUid(int uid) { - return mComputer.getPrivateFlagsForUid(uid); - } - - @Override - public boolean isUidPrivileged(int uid) { - return mComputer.isUidPrivileged(uid); - } - - // NOTE: Can't remove due to unsupported app usage - @NonNull - @Override - public String[] getAppOpPermissionPackages(@NonNull String permissionName) { - return mComputer.getAppOpPermissionPackages(permissionName); - } - - @Override - public ResolveInfo resolveIntent(Intent intent, String resolvedType, - @PackageManager.ResolveInfoFlagsBits long flags, int userId) { - return mResolveIntentHelper.resolveIntentInternal(snapshotComputer(), intent, resolvedType, - flags, 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid()); - } - - @Override - public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) { - return mPreferredActivityHelper.findPersistentPreferredActivity(intent, userId); - } - - @Override - public void setLastChosenActivity(Intent intent, String resolvedType, int flags, - IntentFilter filter, int match, ComponentName activity) { - mPreferredActivityHelper.setLastChosenActivity(intent, resolvedType, flags, - new WatchedIntentFilter(filter), match, activity); - } - - @Override - public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) { - return mPreferredActivityHelper.getLastChosenActivity(intent, resolvedType, flags); - } - private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, @Nullable String callingFeatureId, boolean isRequesterInstantApp, @@ -3394,38 +2894,6 @@ public class PackageManagerService extends IPackageManager.Stub removeMatches, debug, userId, queryMayBeFiltered); } - /* - * Returns if intent can be forwarded from the sourceUserId to the targetUserId - */ - @Override - public boolean canForwardTo(@NonNull Intent intent, @Nullable String resolvedType, - @UserIdInt int sourceUserId, @UserIdInt int targetUserId) { - return mComputer.canForwardTo(intent, resolvedType, sourceUserId, targetUserId); - } - - private UserInfo getProfileParent(int userId) { - return mComputer.getProfileParent(userId); - } - - private List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent, - String resolvedType, int userId) { - return mComputer.getMatchingCrossProfileIntentFilters(intent, - resolvedType, userId); - } - - @Override - public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivities(Intent intent, - String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { - try { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities"); - - return new ParceledListSlice<>(snapshotComputer().queryIntentActivitiesInternal(intent, - resolvedType, flags, userId)); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - } - /** * Returns the package name of the calling Uid if it's an instant app. If it isn't * instant, returns {@code null}. @@ -3434,36 +2902,11 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.getInstantAppPackageName(callingUid); } - @Override - public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivityOptions(ComponentName caller, - Intent[] specifics, String[] specificTypes, Intent intent, - String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { - return new ParceledListSlice<>(mResolveIntentHelper.queryIntentActivityOptionsInternal( - snapshotComputer(), caller, specifics, specificTypes, intent, resolvedType, flags, - userId)); - } - - @Override - public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent, - String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(@NonNull Computer snapshot, + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, + @UserIdInt int userId) { return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal( - snapshotComputer(), intent, resolvedType, flags, userId, Binder.getCallingUid())); - } - - @Override - public ResolveInfo resolveService(Intent intent, String resolvedType, - @PackageManager.ResolveInfoFlagsBits long flags, int userId) { - final int callingUid = Binder.getCallingUid(); - return mResolveIntentHelper.resolveServiceInternal(snapshotComputer(), intent, resolvedType, - flags, userId, callingUid); - } - - @Override - public @NonNull ParceledListSlice<ResolveInfo> queryIntentServices(Intent intent, - String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { - final int callingUid = Binder.getCallingUid(); - return new ParceledListSlice<>(queryIntentServicesInternal( - intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/)); + snapshot, intent, resolvedType, flags, userId, Binder.getCallingUid())); } @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, @@ -3474,163 +2917,16 @@ public class PackageManagerService extends IPackageManager.Stub includeInstantApps); } - @Override - public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent, - String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { - return new ParceledListSlice<>(mResolveIntentHelper.queryIntentContentProvidersInternal( - snapshotComputer(), intent, resolvedType, flags, userId)); - } - - @Override - public ParceledListSlice<PackageInfo> getInstalledPackages( - @PackageManager.PackageInfoFlagsBits long flags, int userId) { - return mComputer.getInstalledPackages(flags, userId); - } - - @Override - public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions( - @NonNull String[] permissions, @PackageManager.PackageInfoFlagsBits long flags, - @UserIdInt int userId) { - return mComputer.getPackagesHoldingPermissions(permissions, flags, userId); - } - - @Override - public ParceledListSlice<ApplicationInfo> getInstalledApplications( - @PackageManager.ApplicationInfoFlagsBits long flags, int userId) { - final int callingUid = Binder.getCallingUid(); - return new ParceledListSlice<>( - mComputer.getInstalledApplications(flags, userId, callingUid)); - } - - @Override - public ParceledListSlice<InstantAppInfo> getInstantApps(int userId) { - if (HIDE_EPHEMERAL_APIS) { - return null; - } - if (!canViewInstantApps(Binder.getCallingUid(), userId)) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS, - "getEphemeralApplications"); - } - enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, - false /* checkShell */, "getEphemeralApplications"); - - Computer computer = snapshotComputer(); - List<InstantAppInfo> instantApps = mInstantAppRegistry.getInstantApps(computer, userId); - if (instantApps != null) { - return new ParceledListSlice<>(instantApps); - } - return null; - } - - @Override - public boolean isInstantApp(String packageName, int userId) { - return mComputer.isInstantApp(packageName, userId); - } - private boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid) { return mComputer.isInstantAppInternal(packageName, userId, callingUid); } - @Override - public byte[] getInstantAppCookie(String packageName, int userId) { - if (HIDE_EPHEMERAL_APIS) { - return null; - } - - enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, - false /* checkShell */, "getInstantAppCookie"); - if (!isCallerSameApp(packageName, Binder.getCallingUid())) { - return null; - } - PackageStateInternal packageState = getPackageStateInternal(packageName); - if (packageState == null || packageState.getPkg() == null) { - return null; - } - return mInstantAppRegistry.getInstantAppCookie(packageState.getPkg(), userId); - } - - @Override - public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId) { - if (HIDE_EPHEMERAL_APIS) { - return true; - } - - enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, - true /* checkShell */, "setInstantAppCookie"); - if (!isCallerSameApp(packageName, Binder.getCallingUid())) { - return false; - } - - PackageStateInternal packageState = getPackageStateInternal(packageName); - if (packageState == null || packageState.getPkg() == null) { - return false; - } - return mInstantAppRegistry.setInstantAppCookie(packageState.getPkg(), cookie, - mContext.getPackageManager().getInstantAppCookieMaxBytes(), userId); - } - - @Override - public Bitmap getInstantAppIcon(String packageName, int userId) { - if (HIDE_EPHEMERAL_APIS) { - return null; - } - - if (!canViewInstantApps(Binder.getCallingUid(), userId)) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS, - "getInstantAppIcon"); - } - enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, - false /* checkShell */, "getInstantAppIcon"); - - return mInstantAppRegistry.getInstantAppIcon(packageName, userId); - } - boolean isCallerSameApp(String packageName, int uid) { return mComputer.isCallerSameApp(packageName, uid); } - @Override - public @NonNull ParceledListSlice<ApplicationInfo> getPersistentApplications(int flags) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return ParceledListSlice.emptyList(); - } - return new ParceledListSlice<>(mComputer.getPersistentApplications(mSafeMode, flags)); - } - - @Override - public ProviderInfo resolveContentProvider(String name, - @PackageManager.ResolveInfoFlagsBits long flags, int userId) { - return mComputer.resolveContentProvider(name, flags, userId, Binder.getCallingUid()); - } - - @Deprecated - public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) { - mComputer.querySyncProviders(mSafeMode, outNames, outInfo); - } - - @NonNull - @Override - public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, - int uid, @PackageManager.ComponentInfoFlagsBits long flags, - @Nullable String metaDataKey) { - return mComputer.queryContentProviders(processName, uid, flags, metaDataKey); - } - - @Nullable - @Override - public InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName component, int flags) { - return mComputer.getInstrumentationInfo(component, flags); - } - - @NonNull - @Override - public ParceledListSlice<InstrumentationInfo> queryInstrumentation( - @NonNull String targetPackage, int flags) { - return mComputer.queryInstrumentation(targetPackage, flags); - } - public static void reportSettingsProblem(int priority, String msg) { logCriticalInfo(priority, msg); } @@ -3680,7 +2976,6 @@ public class PackageManagerService extends IPackageManager.Stub requireFullPermission, checkShell, message); } - @Override public void performFstrimIfNeeded() { PackageManagerServiceUtils.enforceSystemOrRoot("Only the system can request fstrim"); @@ -3722,28 +3017,10 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override public void updatePackagesIfNeeded() { mDexOptHelper.performPackageDexOptUpgradeIfNeeded(); } - @Override - public void notifyPackageUse(String packageName, int reason) { - final int callingUid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getUserId(callingUid); - Computer computer = snapshotComputer(); - final boolean notify; - if (getInstantAppPackageName(callingUid) != null) { - notify = isCallerSameApp(packageName, callingUid); - } else { - notify = !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID); - } - if (!notify) { - return; - } - - notifyPackageUseInternal(packageName, reason); - } private void notifyPackageUseInternal(String packageName, int reason) { long time = System.currentTimeMillis(); @@ -3752,103 +3029,6 @@ public class PackageManagerService extends IPackageManager.Stub }); } - @Override - public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap, - String loaderIsa) { - int callingUid = Binder.getCallingUid(); - if (PLATFORM_PACKAGE_NAME.equals(loadingPackageName) && callingUid != Process.SYSTEM_UID) { - Slog.w(TAG, "Non System Server process reporting dex loads as system server. uid=" - + callingUid); - // Do not record dex loads from processes pretending to be system server. - // Only the system server should be assigned the package "android", so reject calls - // that don't satisfy the constraint. - // - // notifyDexLoad is a PM API callable from the app process. So in theory, apps could - // craft calls to this API and pretend to be system server. Doing so poses no particular - // danger for dex load reporting or later dexopt, however it is a sensible check to do - // in order to verify the expectations. - return; - } - - int userId = UserHandle.getCallingUserId(); - ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); - if (ai == null) { - Slog.w(TAG, "Loading a package that does not exist for the calling user. package=" - + loadingPackageName + ", user=" + userId); - return; - } - mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId, - Process.isIsolated(callingUid)); - } - - @Override - public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule, - IDexModuleRegisterCallback callback) { - int userId = UserHandle.getCallingUserId(); - ApplicationInfo ai = getApplicationInfo(packageName, /*flags*/ 0, userId); - DexManager.RegisterDexModuleResult result; - if (ai == null) { - Slog.w(TAG, "Registering a dex module for a package that does not exist for the" + - " calling user. package=" + packageName + ", user=" + userId); - result = new DexManager.RegisterDexModuleResult(false, "Package not installed"); - } else { - result = mDexManager.registerDexModule(ai, dexModulePath, isSharedModule, userId); - } - - if (callback != null) { - mHandler.post(() -> { - try { - callback.onDexModuleRegistered(dexModulePath, result.success, result.message); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to callback after module registration " + dexModulePath, e); - } - }); - } - } - - /** - * Ask the package manager to perform a dex-opt with the given compiler filter. - * - * Note: exposed only for the shell command to allow moving packages explicitly to a - * definite state. - */ - @Override - public boolean performDexOptMode(String packageName, - boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete, String splitName) { - return mDexOptHelper.performDexOptMode(packageName, checkProfiles, targetCompilerFilter, - force, bootComplete, splitName); - } - - /** - * Ask the package manager to perform a dex-opt with the given compiler filter on the - * secondary dex files belonging to the given package. - * - * Note: exposed only for the shell command to allow moving packages explicitly to a - * definite state. - */ - @Override - public boolean performDexOptSecondary(String packageName, String compilerFilter, - boolean force) { - return mDexOptHelper.performDexOptSecondary(packageName, compilerFilter, force); - } - - /** - * Reconcile the information we have about the secondary dex files belonging to - * {@code packageName} and the actual dex files. For all dex files that were - * deleted, update the internal records and delete the generated oat files. - */ - @Override - public void reconcileSecondaryDexFiles(String packageName) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return; - } else if (isInstantAppInternal( - packageName, UserHandle.getCallingUserId(), Process.SYSTEM_UID)) { - return; - } - mDexManager.reconcileSecondaryDexFiles(packageName); - } - /*package*/ DexManager getDexManager() { return mDexManager; } @@ -3879,67 +3059,10 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public void dumpProfiles(String packageName) { - /* Only the shell, root, or the app user should be able to dump profiles. */ - final int callingUid = Binder.getCallingUid(); - final String[] callerPackageNames = getPackagesForUid(callingUid); - if (callingUid != Process.SHELL_UID - && callingUid != Process.ROOT_UID - && !ArrayUtils.contains(callerPackageNames, packageName)) { - throw new SecurityException("dumpProfiles"); - } - - AndroidPackage pkg = getPackage(packageName); - if (pkg == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - - synchronized (mInstallLock) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles"); - mArtManagerService.dumpProfiles(pkg); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - } - - @Override - public void forceDexOpt(String packageName) { - mDexOptHelper.forceDexOpt(packageName); - } - int[] resolveUserIds(int userId) { return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId }; } - @Override - public Property getProperty(String propertyName, String packageName, String className) { - Objects.requireNonNull(propertyName); - Objects.requireNonNull(packageName); - PackageStateInternal packageState = mComputer.getPackageStateFiltered(packageName, - Binder.getCallingUid(), UserHandle.getCallingUserId()); - if (packageState == null) { - return null; - } - return mPackageProperty.getProperty(propertyName, packageName, className); - } - - @Override - public ParceledListSlice<Property> queryProperty( - String propertyName, @PropertyLocation int componentType) { - Objects.requireNonNull(propertyName); - final int callingUid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getCallingUserId(); - final List<Property> result = - mPackageProperty.queryProperty(propertyName, componentType, packageName -> { - final PackageStateInternal ps = getPackageStateInternal(packageName); - return shouldFilterApplication(ps, callingUid, callingUserId); - }); - if (result == null) { - return ParceledListSlice.emptyList(); - } - return new ParceledListSlice<>(result); - } - private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) { if (installerActivity == null) { if (DEBUG_INSTANT) { @@ -4059,159 +3182,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, - int userId) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); - final int callingUid = Binder.getCallingUid(); - enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, - true /* checkShell */, "setApplicationHiddenSetting for user " + userId); - - if (hidden && isPackageDeviceAdmin(packageName, userId)) { - Slog.w(TAG, "Not hiding package " + packageName + ": has active device admin"); - return false; - } - - // Do not allow "android" is being disabled - if ("android".equals(packageName)) { - Slog.w(TAG, "Cannot hide package: android"); - return false; - } - - final long callingId = Binder.clearCallingIdentity(); - try { - final PackageStateInternal packageState = - mComputer.getPackageStateFiltered(packageName, callingUid, userId); - if (packageState == null) { - return false; - } - - // Cannot hide static shared libs as they are considered - // a part of the using app (emulating static linking). Also - // static libs are installed always on internal storage. - AndroidPackage pkg = packageState.getPkg(); - if (pkg != null) { - // Cannot hide SDK libs as they are controlled by SDK manager. - if (pkg.getSdkLibName() != null) { - Slog.w(TAG, "Cannot hide package: " + packageName - + " providing SDK library: " - + pkg.getSdkLibName()); - return false; - } - // Cannot hide static shared libs as they are considered - // a part of the using app (emulating static linking). Also - // static libs are installed always on internal storage. - if (pkg.getStaticSharedLibName() != null) { - Slog.w(TAG, "Cannot hide package: " + packageName - + " providing static shared library: " - + pkg.getStaticSharedLibName()); - return false; - } - } - // Only allow protected packages to hide themselves. - if (hidden && !UserHandle.isSameApp(callingUid, packageState.getAppId()) - && mProtectedPackages.isPackageStateProtected(userId, packageName)) { - Slog.w(TAG, "Not hiding protected package: " + packageName); - return false; - } - - if (packageState.getUserStateOrDefault(userId).isHidden() == hidden) { - return false; - } - - commitPackageStateMutation(null, packageName, packageState1 -> - packageState1.userState(userId).setHidden(hidden)); - - final PackageStateInternal newPackageState = getPackageStateInternal(packageName); - - if (hidden) { - killApplication(packageName, newPackageState.getAppId(), userId, "hiding pkg"); - sendApplicationHiddenForUser(packageName, newPackageState, userId); - } else { - sendPackageAddedForUser(packageName, newPackageState, userId, DataLoaderType.NONE); - } - - scheduleWritePackageRestrictions(userId); - return true; - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - - @Override - public void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden) { - final int callingUid = Binder.getCallingUid(); - final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID - || callingUid == Process.SYSTEM_UID; - if (!calledFromSystemOrPhone) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, - "setSystemAppHiddenUntilInstalled"); - } - - final PackageStateInternal stateRead = getPackageStateInternal(packageName); - if (stateRead == null || !stateRead.isSystem() || stateRead.getPkg() == null) { - return; - } - if (stateRead.getPkg().isCoreApp() && !calledFromSystemOrPhone) { - throw new SecurityException("Only system or phone callers can modify core apps"); - } - - commitPackageStateMutation(null, mutator -> { - mutator.forPackage(packageName) - .setHiddenUntilInstalled(hidden); - mutator.forDisabledSystemPackage(packageName) - .setHiddenUntilInstalled(hidden); - }); - } - - @Override - public boolean setSystemAppInstallState(String packageName, boolean installed, int userId) { - final int callingUid = Binder.getCallingUid(); - final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID - || callingUid == Process.SYSTEM_UID; - if (!calledFromSystemOrPhone) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, - "setSystemAppHiddenUntilInstalled"); - } - - final PackageStateInternal packageState = getPackageStateInternal(packageName); - // The target app should always be in system - if (packageState == null || !packageState.isSystem() || packageState.getPkg() == null) { - return false; - } - if (packageState.getPkg().isCoreApp() && !calledFromSystemOrPhone) { - throw new SecurityException("Only system or phone callers can modify core apps"); - } - // Check if the install state is the same - if (packageState.getUserStateOrDefault(userId).isInstalled() == installed) { - return false; - } - - final long callingId = Binder.clearCallingIdentity(); - try { - if (installed) { - // install the app from uninstalled state - installExistingPackageAsUser( - packageName, - userId, - PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, - PackageManager.INSTALL_REASON_DEVICE_SETUP, - null); - return true; - } - - // uninstall the app from installed state - deletePackageVersioned( - new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), - new LegacyPackageDeleteObserver(null).getBinder(), - userId, - PackageManager.DELETE_SYSTEM_APP); - return true; - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - private void sendApplicationHiddenForUser(String packageName, PackageStateInternal packageState, int userId) { final PackageRemovedInfo info = new PackageRemovedInfo(this); @@ -4223,26 +3193,6 @@ public class PackageManagerService extends IPackageManager.Stub info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/); } - /** - * Returns true if application is not found or there was an error. Otherwise it returns - * the hidden state of the package for the given user. - */ - @Override - public boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, - @UserIdInt int userId) { - return mComputer.getApplicationHiddenSettingAsUser(packageName, userId); - } - - /** - * @hide - */ - @Override - public int installExistingPackageAsUser(String packageName, int userId, int installFlags, - int installReason, List<String> whiteListedPermissions) { - return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags, - installReason, whiteListedPermissions, null); - } - boolean isUserRestricted(int userId, String restrictionKey) { Bundle restrictions = mUserManager.getUserRestrictions(userId); if (restrictions.getBoolean(restrictionKey, false)) { @@ -4252,77 +3202,6 @@ public class PackageManagerService extends IPackageManager.Stub return false; } - @Override - public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames, - int restrictionFlags, int userId) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, - "setDistractingPackageRestrictionsAsUser"); - - final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID - && UserHandle.getUserId(callingUid) != userId) { - throw new SecurityException("Calling uid " + callingUid + " cannot call for user " - + userId); - } - Objects.requireNonNull(packageNames, "packageNames cannot be null"); - if (restrictionFlags != 0 - && !mSuspendPackageHelper.isSuspendAllowedForUser(userId, callingUid)) { - Slog.w(TAG, "Cannot restrict packages due to restrictions on user " + userId); - return packageNames; - } - - final List<String> changedPackagesList = new ArrayList<>(packageNames.length); - final IntArray changedUids = new IntArray(packageNames.length); - final List<String> unactionedPackages = new ArrayList<>(packageNames.length); - - ArraySet<String> changesToCommit = new ArraySet<>(); - Computer computer = snapshotComputer(); - final boolean[] canRestrict = (restrictionFlags != 0) - ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId, - callingUid) : null; - for (int i = 0; i < packageNames.length; i++) { - final String packageName = packageNames[i]; - final PackageStateInternal packageState = - computer.getPackageStateInternal(packageName); - if (packageState == null - || computer.shouldFilterApplication(packageState, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageName - + ". Skipping..."); - unactionedPackages.add(packageName); - continue; - } - if (canRestrict != null && !canRestrict[i]) { - unactionedPackages.add(packageName); - continue; - } - final int oldDistractionFlags = packageState.getUserStateOrDefault(userId) - .getDistractionFlags(); - if (restrictionFlags != oldDistractionFlags) { - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); - changesToCommit.add(packageName); - } - } - - commitPackageStateMutation(null, mutator -> { - final int size = changesToCommit.size(); - for (int index = 0; index < size; index++) { - mutator.forPackage(changesToCommit.valueAt(index)) - .userState(userId) - .setDistractionFlags(restrictionFlags); - } - }); - - if (!changedPackagesList.isEmpty()) { - final String[] changedPackages = changedPackagesList.toArray( - new String[changedPackagesList.size()]); - mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged( - changedPackages, changedUids.toArray(), userId, restrictionFlags)); - scheduleWritePackageRestrictions(userId); - } - return unactionedPackages.toArray(new String[0]); - } - private void enforceCanSetPackagesSuspendedAsUser(String callingPackage, int callingUid, int userId, String callingMethod) { if (callingUid == Process.ROOT_UID @@ -4333,7 +3212,7 @@ public class PackageManagerService extends IPackageManager.Stub final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId); if (ownerPackage != null) { - final int ownerUid = getPackageUid(ownerPackage, 0, userId); + final int ownerUid = mIPackageManager.getPackageUid(ownerPackage, 0, userId); if (ownerUid == callingUid) { return; } @@ -4342,7 +3221,7 @@ public class PackageManagerService extends IPackageManager.Stub mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, callingMethod); - final int packageUid = getPackageUid(callingPackage, 0, userId); + final int packageUid = mIPackageManager.getPackageUid(callingPackage, 0, userId); final boolean allowedPackageUid = packageUid == callingUid; // TODO(b/139383163): remove special casing for shell and enforce INTERACT_ACROSS_USERS_FULL final boolean allowedShell = callingUid == SHELL_UID @@ -4354,34 +3233,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, - PersistableBundle appExtras, PersistableBundle launcherExtras, - SuspendDialogInfo dialogInfo, String callingPackage, int userId) { - final int callingUid = Binder.getCallingUid(); - enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId, - "setPackagesSuspendedAsUser"); - return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames, - suspended, appExtras, launcherExtras, dialogInfo, callingPackage, userId, - callingUid); - } - - @Override - public Bundle getSuspendedPackageAppExtras(String packageName, int userId) { - final int callingUid = Binder.getCallingUid(); - if (getPackageUid(packageName, 0, userId) != callingUid) { - throw new SecurityException("Calling package " + packageName - + " does not belong to calling uid " + callingUid); - } - return mSuspendPackageHelper.getSuspendedPackageAppExtras( - packageName, userId, callingUid); - } - - @Override - public boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId) { - return mComputer.isPackageSuspendedForUser(packageName, userId); - } - void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage, @UserIdInt int userId) { // TODO: This can be replaced by a special parameter to iterate all packages, rather than @@ -4436,67 +3287,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) { - Objects.requireNonNull(packageNames, "packageNames cannot be null"); - mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, - "getUnsuspendablePackagesForUser"); - final int callingUid = Binder.getCallingUid(); - if (UserHandle.getUserId(callingUid) != userId) { - throw new SecurityException("Calling uid " + callingUid - + " cannot query getUnsuspendablePackagesForUser for user " + userId); - } - return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(), - packageNames, userId, callingUid); - } - - @Override - public void verifyPendingInstall(int id, int verificationCode) throws RemoteException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, - "Only package verification agents can verify applications"); - final int callingUid = Binder.getCallingUid(); - - final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED); - final PackageVerificationResponse response = new PackageVerificationResponse( - verificationCode, callingUid); - msg.arg1 = id; - msg.obj = response; - mHandler.sendMessage(msg); - } - - @Override - public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, - long millisecondsToDelay) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, - "Only package verification agents can extend verification timeouts"); - final int callingUid = Binder.getCallingUid(); - - mHandler.post(() -> { - final PackageVerificationState state = mPendingVerification.get(id); - final PackageVerificationResponse response = new PackageVerificationResponse( - verificationCodeAtTimeout, callingUid); - - long delay = millisecondsToDelay; - if (delay > PackageManager.MAXIMUM_VERIFICATION_TIMEOUT) { - delay = PackageManager.MAXIMUM_VERIFICATION_TIMEOUT; - } - if (delay < 0) { - delay = 0; - } - - if ((state != null) && !state.timeoutExtended()) { - state.extendTimeout(); - - final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED); - msg.arg1 = id; - msg.obj = response; - mHandler.sendMessageDelayed(msg, delay); - } - }); - } - private void setEnableRollbackCode(int token, int enableRollbackCode) { final Message msg = mHandler.obtainMessage(ENABLE_ROLLBACK_STATUS); msg.arg1 = token; @@ -4504,223 +3294,6 @@ public class PackageManagerService extends IPackageManager.Stub mHandler.sendMessage(msg); } - @Override - public void finishPackageInstall(int token, boolean didLaunch) { - PackageManagerServiceUtils.enforceSystemOrRoot( - "Only the system is allowed to finish installs"); - - if (DEBUG_INSTALL) { - Slog.v(TAG, "BM finishing package install for " + token); - } - Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "restore", token); - - final Message msg = mHandler.obtainMessage(POST_INSTALL, token, didLaunch ? 1 : 0); - mHandler.sendMessage(msg); - } - - @Deprecated - @Override - public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) { - DomainVerificationProxyV1.queueLegacyVerifyResult(mContext, mDomainVerificationConnection, - id, verificationCode, failedDomains, Binder.getCallingUid()); - } - - @Deprecated - @Override - public int getIntentVerificationStatus(String packageName, int userId) { - return mDomainVerificationManager.getLegacyState(packageName, userId); - } - - @Deprecated - @Override - public boolean updateIntentVerificationStatus(String packageName, int status, int userId) { - return mDomainVerificationManager.setLegacyUserState(packageName, userId, status); - } - - @Deprecated - @Override - public @NonNull ParceledListSlice<IntentFilterVerificationInfo> getIntentFilterVerifications( - String packageName) { - return ParceledListSlice.emptyList(); - } - - @NonNull - @Override - public ParceledListSlice<IntentFilter> getAllIntentFilters(@NonNull String packageName) { - return mComputer.getAllIntentFilters(packageName); - } - - @Override - public void setInstallerPackageName(String targetPackage, String installerPackageName) { - final int callingUid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getUserId(callingUid); - final FunctionalUtils.ThrowingCheckedFunction<Computer, Boolean, RuntimeException> - implementation = computer -> { - if (computer.getInstantAppPackageName(callingUid) != null) { - return false; - } - - PackageStateInternal targetPackageState = - computer.getPackageStateInternal(targetPackage); - if (targetPackageState == null - || computer.shouldFilterApplication(targetPackageState, callingUid, - callingUserId)) { - throw new IllegalArgumentException("Unknown target package: " + targetPackage); - } - - PackageStateInternal installerPackageState = null; - if (installerPackageName != null) { - installerPackageState = computer.getPackageStateInternal(installerPackageName); - if (installerPackageState == null - || shouldFilterApplication( - installerPackageState, callingUid, callingUserId)) { - throw new IllegalArgumentException("Unknown installer package: " - + installerPackageName); - } - } - - Signature[] callerSignature; - final int appId = UserHandle.getAppId(callingUid); - Pair<PackageStateInternal, SharedUserApi> either = - computer.getPackageOrSharedUser(appId); - if (either != null) { - if (either.first != null) { - callerSignature = either.first.getSigningDetails().getSignatures(); - } else { - callerSignature = either.second.getSigningDetails().getSignatures(); - } - } else { - throw new SecurityException("Unknown calling UID: " + callingUid); - } - - // Verify: can't set installerPackageName to a package that is - // not signed with the same cert as the caller. - if (installerPackageState != null) { - if (compareSignatures(callerSignature, - installerPackageState.getSigningDetails().getSignatures()) - != PackageManager.SIGNATURE_MATCH) { - throw new SecurityException( - "Caller does not have same cert as new installer package " - + installerPackageName); - } - } - - // Verify: if target already has an installer package, it must - // be signed with the same cert as the caller. - String targetInstallerPackageName = - targetPackageState.getInstallSource().installerPackageName; - PackageStateInternal targetInstallerPkgSetting = targetInstallerPackageName == null - ? null : computer.getPackageStateInternal(targetInstallerPackageName); - - if (targetInstallerPkgSetting != null) { - if (compareSignatures(callerSignature, - targetInstallerPkgSetting.getSigningDetails().getSignatures()) - != PackageManager.SIGNATURE_MATCH) { - throw new SecurityException( - "Caller does not have same cert as old installer package " - + targetInstallerPackageName); - } - } else if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) - != PERMISSION_GRANTED) { - // This is probably an attempt to exploit vulnerability b/150857253 of taking - // privileged installer permissions when the installer has been uninstalled or - // was never set. - EventLog.writeEvent(0x534e4554, "150857253", callingUid, ""); - - final long binderToken = Binder.clearCallingIdentity(); - try { - if (mInjector.getCompatibility().isChangeEnabledByUid( - THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE, - callingUid)) { - throw new SecurityException("Neither user " + callingUid - + " nor current process has " - + Manifest.permission.INSTALL_PACKAGES); - } else { - // If change disabled, fail silently for backwards compatibility - return false; - } - } finally { - Binder.restoreCallingIdentity(binderToken); - } - } - - return true; - }; - PackageStateMutator.InitialState initialState = recordInitialState(); - boolean allowed = implementation.apply(snapshotComputer()); - if (allowed) { - // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames, - // should find an alternative which avoids any race conditions - PackageStateInternal targetPackageState; - synchronized (mLock) { - PackageStateMutator.Result result = commitPackageStateMutation(initialState, - targetPackage, state -> state.setInstaller(installerPackageName)); - if (result.isPackagesChanged() || result.isStateChanged()) { - synchronized (mPackageStateWriteLock) { - allowed = implementation.apply(snapshotComputer()); - if (allowed) { - commitPackageStateMutation(null, targetPackage, - state -> state.setInstaller(installerPackageName)); - } else { - return; - } - } - } - targetPackageState = getPackageStateInternal(targetPackage); - mSettings.addInstallerPackageNames(targetPackageState.getInstallSource()); - } - mAppsFilter.addPackage(targetPackageState); - scheduleWriteSettings(); - } - } - - @Override - public void setApplicationCategoryHint(String packageName, int categoryHint, - String callerPackageName) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - throw new SecurityException("Instant applications don't have access to this method"); - } - mInjector.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(), - callerPackageName); - - final PackageStateMutator.InitialState initialState = recordInitialState(); - - final FunctionalUtils.ThrowingFunction<Computer, PackageStateMutator.Result> - implementation = computer -> { - PackageStateInternal packageState = computer.getPackageStateFiltered(packageName, - Binder.getCallingUid(), UserHandle.getCallingUserId()); - if (packageState == null) { - throw new IllegalArgumentException("Unknown target package " + packageName); - } - - if (!Objects.equals(callerPackageName, - packageState.getInstallSource().installerPackageName)) { - throw new IllegalArgumentException("Calling package " + callerPackageName - + " is not installer for " + packageName); - } - - if (packageState.getCategoryOverride() != categoryHint) { - return commitPackageStateMutation(initialState, - packageName, state -> state.setCategoryOverride(categoryHint)); - } else { - return null; - } - }; - - PackageStateMutator.Result result = implementation.apply(snapshotComputer()); - if (result != null && result.isStateChanged() && !result.isSpecificPackageNull()) { - // TODO: Specific return value of what state changed? - // The installer on record might have changed, retry with lock - synchronized (mPackageStateWriteLock) { - result = implementation.apply(snapshotComputer()); - } - } - - if (result != null && result.isCommitted()) { - scheduleWriteSettings(); - } - } - /** * Callback from PackageSettings whenever an app is first transitioned out of the * 'stopped' state. Normally we just issue the broadcast, but we can't do that if @@ -4799,21 +3372,12 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public void deletePackageAsUser(String packageName, int versionCode, - IPackageDeleteObserver observer, int userId, int flags) { - deletePackageVersioned(new VersionedPackage(packageName, versionCode), - new LegacyPackageDeleteObserver(observer).getBinder(), userId, flags); - } - - @Override public void deleteExistingPackageAsUser(VersionedPackage versionedPackage, final IPackageDeleteObserver2 observer, final int userId) { mDeletePackageHelper.deleteExistingPackageAsUser( versionedPackage, observer, userId); } - @Override public void deletePackageVersioned(VersionedPackage versionedPackage, final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) { mDeletePackageHelper.deletePackageVersionedInternal( @@ -4830,15 +3394,14 @@ public class PackageManagerService extends IPackageManager.Stub boolean isCallerVerifier(int callingUid) { final int callingUserId = UserHandle.getUserId(callingUid); - return mRequiredVerifierPackage != null && - callingUid == getPackageUid(mRequiredVerifierPackage, 0, callingUserId); + return mRequiredVerifierPackage != null && callingUid == mIPackageManager.getPackageUid( + mRequiredVerifierPackage, 0, callingUserId); } - @Override public boolean isPackageDeviceAdminOnAnyUser(String packageName) { final int callingUid = Binder.getCallingUid(); - if (checkUidPermission(android.Manifest.permission.MANAGE_USERS, callingUid) - != PERMISSION_GRANTED) { + if (mIPackageManager.checkUidPermission(android.Manifest.permission.MANAGE_USERS, + callingUid) != PERMISSION_GRANTED) { EventLog.writeEvent(0x534e4554, "128599183", -1, ""); throw new SecurityException(android.Manifest.permission.MANAGE_USERS + " permission is required to call this API"); @@ -4893,139 +3456,6 @@ public class PackageManagerService extends IPackageManager.Stub return mDevicePolicyManager; } - @Override - public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, - int userId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.DELETE_PACKAGES, null); - PackageStateInternal packageState = getPackageStateInternal(packageName); - if (packageState != null && packageState.getPkg() != null) { - AndroidPackage pkg = packageState.getPkg(); - // Cannot block uninstall SDK libs as they are controlled by SDK manager. - if (pkg.getSdkLibName() != null) { - Slog.w(TAG, "Cannot block uninstall of package: " + packageName - + " providing SDK library: " + pkg.getSdkLibName()); - return false; - } - // Cannot block uninstall of static shared libs as they are - // considered a part of the using app (emulating static linking). - // Also static libs are installed always on internal storage. - if (pkg.getStaticSharedLibName() != null) { - Slog.w(TAG, "Cannot block uninstall of package: " + packageName - + " providing static shared library: " + pkg.getStaticSharedLibName()); - return false; - } - } - synchronized (mLock) { - mSettings.setBlockUninstallLPw(userId, packageName, blockUninstall); - } - - scheduleWritePackageRestrictions(userId); - return true; - } - - @Override - public boolean getBlockUninstallForUser(@NonNull String packageName, @UserIdInt int userId) { - return mComputer.getBlockUninstallForUser(packageName, userId); - } - - @Override - public boolean setRequiredForSystemUser(String packageName, boolean requiredForSystemUser) { - PackageManagerServiceUtils.enforceSystemOrRoot( - "setRequiredForSystemUser can only be run by the system or root"); - - PackageStateMutator.Result result = commitPackageStateMutation(null, packageName, - packageState -> packageState.setRequiredForSystemUser(requiredForSystemUser)); - if (!result.isCommitted()) { - return false; - } - - scheduleWriteSettings(); - return true; - } - - @Override - public void clearApplicationProfileData(String packageName) { - PackageManagerServiceUtils.enforceSystemOrRoot( - "Only the system can clear all profile data"); - - final AndroidPackage pkg = getPackage(packageName); - try (PackageFreezer ignored = freezePackage(packageName, "clearApplicationProfileData")) { - synchronized (mInstallLock) { - mAppDataHelper.clearAppProfilesLIF(pkg); - } - } - } - - @Override - public void clearApplicationUserData(final String packageName, - final IPackageDataObserver observer, final int userId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CLEAR_APP_USER_DATA, null); - - final int callingUid = Binder.getCallingUid(); - enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, - false /* checkShell */, "clear application data"); - - if (mComputer.getPackageStateFiltered(packageName, callingUid, userId) == null) { - if (observer != null) { - mHandler.post(() -> { - try { - observer.onRemoveCompleted(packageName, false); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } - }); - } - return; - } - if (mProtectedPackages.isPackageDataProtected(userId, packageName)) { - throw new SecurityException("Cannot clear data for a protected package: " - + packageName); - } - - // Queue up an async operation since the package deletion may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - final boolean succeeded; - try (PackageFreezer freezer = freezePackage(packageName, - "clearApplicationUserData")) { - synchronized (mInstallLock) { - succeeded = clearApplicationUserDataLIF(packageName, userId); - } - mInstantAppRegistry.deleteInstantApplicationMetadata(packageName, userId); - synchronized (mLock) { - if (succeeded) { - resetComponentEnabledSettingsIfNeededLPw(packageName, userId); - } - } - } - if (succeeded) { - // invoke DeviceStorageMonitor's update method to clear any notifications - DeviceStorageMonitorInternal dsm = LocalServices - .getService(DeviceStorageMonitorInternal.class); - if (dsm != null) { - dsm.checkMemory(); - } - if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) - == PERMISSION_GRANTED) { - unsuspendForSuspendingPackage(snapshotComputer(), packageName, userId); - removeAllDistractingPackageRestrictions(userId); - flushPackageRestrictionsAsUserInternalLocked(userId); - } - } - if (observer != null) { - try { - observer.onRemoveCompleted(packageName, succeeded); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } - } //end if observer - } //end run - }); - } - private boolean clearApplicationUserDataLIF(String packageName, int userId) { if (packageName == null) { Slog.w(TAG, "Attempt to delete null packageName."); @@ -5108,106 +3538,14 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public void deleteApplicationCacheFiles(final String packageName, - final IPackageDataObserver observer) { - final int userId = UserHandle.getCallingUserId(); - deleteApplicationCacheFilesAsUser(packageName, userId, observer); - } - - @Override - public void deleteApplicationCacheFilesAsUser(final String packageName, final int userId, - final IPackageDataObserver observer) { - final int callingUid = Binder.getCallingUid(); - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.INTERNAL_DELETE_CACHE_FILES) - != PackageManager.PERMISSION_GRANTED) { - // If the caller has the old delete cache permission, silently ignore. Else throw. - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.DELETE_CACHE_FILES) - == PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Calling uid " + callingUid + " does not have " + - android.Manifest.permission.INTERNAL_DELETE_CACHE_FILES + - ", silently ignoring"); - return; - } - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERNAL_DELETE_CACHE_FILES, null); - } - enforceCrossUserPermission(callingUid, userId, /* requireFullPermission= */ true, - /* checkShell= */ false, "delete application cache files"); - final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.ACCESS_INSTANT_APPS); - - final AndroidPackage pkg = getPackage(packageName); - - // Queue up an async operation since the package deletion may take a little while. - mHandler.post(() -> { - final PackageStateInternal ps = - pkg == null ? null : getPackageStateInternal(pkg.getPackageName()); - boolean doClearData = true; - if (ps != null) { - final boolean targetIsInstantApp = - ps.getUserStateOrDefault(UserHandle.getUserId(callingUid)).isInstantApp(); - doClearData = !targetIsInstantApp - || hasAccessInstantApps == PackageManager.PERMISSION_GRANTED; - } - if (doClearData) { - synchronized (mInstallLock) { - final int flags = FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL; - // We're only clearing cache files, so we don't care if the - // app is unfrozen and still able to run - mAppDataHelper.clearAppDataLIF(pkg, userId, - flags | Installer.FLAG_CLEAR_CACHE_ONLY); - mAppDataHelper.clearAppDataLIF(pkg, userId, - flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); - } - } - if (observer != null) { - try { - observer.onRemoveCompleted(packageName, true); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } - } - }); - } - - @Override - public void getPackageSizeInfo(final String packageName, int userId, - final IPackageStatsObserver observer) { - throw new UnsupportedOperationException( - "Shame on you for calling the hidden API getPackageSizeInfo(). Shame!"); - } - int getUidTargetSdkVersion(int uid) { return mComputer.getUidTargetSdkVersion(uid); } - @Override - public void addPreferredActivity(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity, int userId, boolean removeExisting) { - mPreferredActivityHelper.addPreferredActivity( - new WatchedIntentFilter(filter), match, set, activity, true, userId, - "Adding preferred", removeExisting); - } - void postPreferredActivityChangedBroadcast(int userId) { mHandler.post(() -> mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId)); } - @Override - public void replacePreferredActivity(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity, int userId) { - mPreferredActivityHelper.replacePreferredActivity(new WatchedIntentFilter(filter), match, - set, activity, userId); - } - - @Override - public void clearPackagePreferredActivities(String packageName) { - mPreferredActivityHelper.clearPackagePreferredActivities(packageName); - } - /** This method takes a specific user id as well as UserHandle.USER_ALL. */ @GuardedBy("mLock") @@ -5227,109 +3565,6 @@ public class PackageManagerService extends IPackageManager.Stub mPreferredActivityHelper.updateDefaultHomeNotLocked(userId); } - @Override - public void resetApplicationPreferences(int userId) { - mPreferredActivityHelper.resetApplicationPreferences(userId); - } - - @Override - public int getPreferredActivities(List<IntentFilter> outFilters, - List<ComponentName> outActivities, String packageName) { - return mPreferredActivityHelper.getPreferredActivities(outFilters, outActivities, - packageName, mComputer); - } - - @Override - public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity, - int userId) { - mPreferredActivityHelper.addPersistentPreferredActivity(new WatchedIntentFilter(filter), - activity, userId); - } - - @Override - public void clearPackagePersistentPreferredActivities(String packageName, int userId) { - mPreferredActivityHelper.clearPackagePersistentPreferredActivities(packageName, userId); - } - - /** - * Non-Binder method, support for the backup/restore mechanism: write the - * full set of preferred activities in its canonical XML format. Returns the - * XML output as a byte array, or null if there is none. - */ - @Override - public byte[] getPreferredActivityBackup(int userId) { - return mPreferredActivityHelper.getPreferredActivityBackup(userId); - } - - @Override - public void restorePreferredActivities(byte[] backup, int userId) { - mPreferredActivityHelper.restorePreferredActivities(backup, userId); - } - - /** - * Non-Binder method, support for the backup/restore mechanism: write the - * default browser (etc) settings in its canonical XML format. Returns the default - * browser XML representation as a byte array, or null if there is none. - */ - @Override - public byte[] getDefaultAppsBackup(int userId) { - return mPreferredActivityHelper.getDefaultAppsBackup(userId); - } - - @Override - public void restoreDefaultApps(byte[] backup, int userId) { - mPreferredActivityHelper.restoreDefaultApps(backup, userId); - } - - @Override - public byte[] getDomainVerificationBackup(int userId) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Only the system may call getDomainVerificationBackup()"); - } - - try { - try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { - TypedXmlSerializer serializer = Xml.resolveSerializer(output); - mDomainVerificationManager.writeSettings(snapshotComputer(), serializer, true, - userId); - return output.toByteArray(); - } - } catch (Exception e) { - if (DEBUG_BACKUP) { - Slog.e(TAG, "Unable to write domain verification for backup", e); - } - return null; - } - } - - @Override - public void restoreDomainVerification(byte[] backup, int userId) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Only the system may call restorePreferredActivities()"); - } - - try { - ByteArrayInputStream input = new ByteArrayInputStream(backup); - TypedXmlPullParser parser = Xml.resolvePullParser(input); - - // User ID input isn't necessary here as it assumes the user integers match and that - // the only states inside the backup XML are for the target user. - mDomainVerificationManager.restoreSettings(snapshotComputer(), parser); - input.close(); - } catch (Exception e) { - if (DEBUG_BACKUP) { - Slog.e(TAG, "Exception restoring domain verification: " + e.getMessage()); - } - } - } - - @Override - public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage, - int sourceUserId, int targetUserId, int flags) { - addCrossProfileIntentFilter(new WatchedIntentFilter(intentFilter), ownerPackage, - sourceUserId, targetUserId, flags); - } - /** * Variant that takes a {@link WatchedIntentFilter} */ @@ -5365,55 +3600,25 @@ public class PackageManagerService extends IPackageManager.Stub scheduleWritePackageRestrictions(sourceUserId); } - @Override - public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); - final int callingUid = Binder.getCallingUid(); - enforceOwnerRights(ownerPackage, callingUid); - PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(), - UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId); - synchronized (mLock) { - CrossProfileIntentResolver resolver = - mSettings.editCrossProfileIntentResolverLPw(sourceUserId); - ArraySet<CrossProfileIntentFilter> set = - new ArraySet<>(resolver.filterSet()); - for (CrossProfileIntentFilter filter : set) { - if (filter.getOwnerPackage().equals(ownerPackage)) { - resolver.removeFilter(filter); - } - } - } - scheduleWritePackageRestrictions(sourceUserId); - } - // Enforcing that callingUid is owning pkg on userId private void enforceOwnerRights(String pkg, int callingUid) { // The system owns everything. if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { return; } - final String[] callerPackageNames = getPackagesForUid(callingUid); + final String[] callerPackageNames = mIPackageManager.getPackagesForUid(callingUid); if (!ArrayUtils.contains(callerPackageNames, pkg)) { throw new SecurityException("Calling uid " + callingUid + " does not own package " + pkg); } final int callingUserId = UserHandle.getUserId(callingUid); - PackageInfo pi = getPackageInfo(pkg, 0, callingUserId); + PackageInfo pi = mIPackageManager.getPackageInfo(pkg, 0, callingUserId); if (pi == null) { throw new IllegalArgumentException("Unknown package " + pkg + " on user " + callingUserId); } } - @Override - public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return null; - } - return getHomeActivitiesAsUser(allHomeCandidates, UserHandle.getCallingUserId()); - } - public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId) { UserManagerService ums = UserManagerService.getInstance(); if (ums == null || sessionInfo.isStaged()) { @@ -5444,11 +3649,6 @@ public class PackageManagerService extends IPackageManager.Stub userId); } - @Override - public void setHomeActivity(ComponentName comp, int userId) { - mPreferredActivityHelper.setHomeActivity(comp, userId); - } - private @Nullable String getSetupWizardPackageNameImpl(@NonNull Computer computer) { final Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_SETUP_WIZARD); @@ -5500,84 +3700,17 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public String getDefaultTextClassifierPackageName() { - return ensureSystemPackageName( - mContext.getString(R.string.config_servicesExtensionPackage)); - } - - @Override - public String getSystemTextClassifierPackageName() { - return ensureSystemPackageName( - mContext.getString(R.string.config_defaultTextClassifierPackage)); - } - - @Override - public @Nullable String getAttentionServicePackageName() { - return ensureSystemPackageName( - getPackageFromComponentString(R.string.config_defaultAttentionService)); - } - - @Override - public @Nullable String getRotationResolverPackageName() { - return ensureSystemPackageName( - getPackageFromComponentString(R.string.config_defaultRotationResolverService)); - } - @Nullable private String getDeviceConfiguratorPackageName() { return ensureSystemPackageName(mContext.getString( R.string.config_deviceConfiguratorPackageName)); } - @Override - public String getWellbeingPackageName() { - final long identity = Binder.clearCallingIdentity(); - try { - return CollectionUtils.firstOrNull( - mContext.getSystemService(RoleManager.class).getRoleHolders( - RoleManager.ROLE_SYSTEM_WELLBEING)); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public String getAppPredictionServicePackageName() { - return ensureSystemPackageName( - getPackageFromComponentString(R.string.config_defaultAppPredictionService)); - } - - @Override - public String getSystemCaptionsServicePackageName() { - return ensureSystemPackageName( - getPackageFromComponentString(R.string.config_defaultSystemCaptionsService)); - } - - @Override - public String getSetupWizardPackageName() { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Non-system caller"); - } - return mPmInternal.getSetupWizardPackageName(); - } - public @Nullable String getAmbientContextDetectionPackageName() { return ensureSystemPackageName(getPackageFromComponentString( R.string.config_defaultAmbientContextDetectionService)); } - public String getIncidentReportApproverPackageName() { - return ensureSystemPackageName(mContext.getString( - R.string.config_incidentReportApproverPackage)); - } - - @Override - public String getContentCaptureServicePackageName() { - return ensureSystemPackageName( - getPackageFromComponentString(R.string.config_defaultContentCaptureService)); - } - public String getOverlayConfigSignaturePackageName() { return ensureSystemPackageName(mInjector.getSystemConfig() .getOverlayConfigSignaturePackage()); @@ -5645,8 +3778,10 @@ public class PackageManagerService extends IPackageManager.Stub } final long token = Binder.clearCallingIdentity(); try { - if (getPackageInfo(packageName, MATCH_FACTORY_ONLY, UserHandle.USER_SYSTEM) == null) { - PackageInfo packageInfo = getPackageInfo(packageName, 0, UserHandle.USER_SYSTEM); + if (mIPackageManager.getPackageInfo(packageName, MATCH_FACTORY_ONLY, + UserHandle.USER_SYSTEM) == null) { + PackageInfo packageInfo = + mIPackageManager.getPackageInfo(packageName, 0, UserHandle.USER_SYSTEM); if (packageInfo != null) { EventLog.writeEvent(0x534e4554, "145981139", packageInfo.applicationInfo.uid, ""); @@ -5659,39 +3794,6 @@ public class PackageManagerService extends IPackageManager.Stub return packageName; } - @Override - public void setApplicationEnabledSetting(String appPackageName, - int newState, int flags, int userId, String callingPackage) { - if (!mUserManager.exists(userId)) return; - if (callingPackage == null) { - callingPackage = Integer.toString(Binder.getCallingUid()); - } - - setEnabledSettings(List.of(new ComponentEnabledSetting(appPackageName, newState, flags)), - userId, callingPackage); - } - - @Override - public void setUpdateAvailable(String packageName, boolean updateAvailable) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); - commitPackageStateMutation(null, packageName, state -> - state.setUpdateAvailable(updateAvailable)); - } - - @Override - public void overrideLabelAndIcon(@NonNull ComponentName componentName, - @NonNull String nonLocalizedLabel, int icon, int userId) { - if (TextUtils.isEmpty(nonLocalizedLabel)) { - throw new IllegalArgumentException("Override label should be a valid String"); - } - updateComponentLabelIcon(componentName, nonLocalizedLabel, icon, userId); - } - - @Override - public void restoreLabelAndIcon(@NonNull ComponentName componentName, int userId) { - updateComponentLabelIcon(componentName, null, null, userId); - } - @VisibleForTesting(visibility = Visibility.PRIVATE) public void updateComponentLabelIcon(/*@NonNull*/ ComponentName componentName, @Nullable String nonLocalizedLabel, @Nullable Integer icon, int userId) { @@ -5758,25 +3860,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public void setComponentEnabledSetting(ComponentName componentName, - int newState, int flags, int userId) { - if (!mUserManager.exists(userId)) return; - - setEnabledSettings(List.of(new ComponentEnabledSetting(componentName, newState, flags)), - userId, null /* callingPackage */); - } - - @Override - public void setComponentEnabledSettings(List<ComponentEnabledSetting> settings, int userId) { - if (!mUserManager.exists(userId)) return; - if (settings == null || settings.isEmpty()) { - throw new IllegalArgumentException("The list of enabled settings is empty"); - } - - setEnabledSettings(settings, userId, null /* callingPackage */); - } - private void setEnabledSettings(List<ComponentEnabledSetting> settings, int userId, String callingPackage) { final int callingUid = Binder.getCallingUid(); @@ -5846,7 +3929,7 @@ public class PackageManagerService extends IPackageManager.Stub continue; } final boolean isCallerTargetApp = ArrayUtils.contains( - getPackagesForUid(callingUid), packageName); + mIPackageManager.getPackagesForUid(callingUid), packageName); final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); // Limit who can change which apps if (!isCallerTargetApp) { @@ -6054,8 +4137,8 @@ public class PackageManagerService extends IPackageManager.Stub pkgSetting.setEnabled(newState, userId, callingPackage); if ((newState == COMPONENT_ENABLED_STATE_DISABLED_USER || newState == COMPONENT_ENABLED_STATE_DISABLED) - && checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) - == PERMISSION_GRANTED) { + && mIPackageManager.checkPermission(Manifest.permission.SUSPEND_APPS, + packageName, userId) == PERMISSION_GRANTED) { // This app should not generally be allowed to get disabled by the UI, but // if it ever does, we don't want to end up with some of the user's apps // permanently suspended. @@ -6098,22 +4181,6 @@ public class PackageManagerService extends IPackageManager.Stub return true; } - @WorkerThread - @Override - public void flushPackageRestrictionsAsUser(int userId) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return; - } - if (!mUserManager.exists(userId)) { - return; - } - enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/, - false /* checkShell */, "flushPackageRestrictions"); - synchronized (mLock) { - flushPackageRestrictionsAsUserInternalLocked(userId); - } - } - @GuardedBy("mLock") private void flushPackageRestrictionsAsUserInternalLocked(int userId) { // NOTE: this invokes synchronous disk access, so callers using this @@ -6146,100 +4213,17 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.getBroadcastAllowList(packageName, userIds, isInstantApp); } - @Override - public void setPackageStoppedState(String packageName, boolean stopped, int userId) { - if (!mUserManager.exists(userId)) return; - final int callingUid = Binder.getCallingUid(); - final Computer computer = snapshotComputer(); - if (computer.getInstantAppPackageName(callingUid) == null) { - final int permission = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); - final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); - if (!allowedByPermission - && !ArrayUtils.contains(computer.getPackagesForUid(callingUid), packageName)) { - throw new SecurityException( - "Permission Denial: attempt to change stopped state from pid=" - + Binder.getCallingPid() - + ", uid=" + callingUid + ", package=" + packageName); - } - computer.enforceCrossUserPermission(callingUid, userId, - true /* requireFullPermission */, true /* checkShell */, "stop package"); - - final PackageStateInternal packageState = computer.getPackageStateInternal(packageName); - final PackageUserState packageUserState = packageState == null - ? null : packageState.getUserStateOrDefault(userId); - if (packageState != null - && !computer.shouldFilterApplication(packageState, callingUid, userId) - && packageUserState.isStopped() != stopped) { - boolean wasNotLaunched = packageUserState.isNotLaunched(); - commitPackageStateMutation(null, packageName, state -> { - PackageUserStateWrite userState = state.userState(userId); - userState.setStopped(stopped); - if (wasNotLaunched) { - userState.setNotLaunched(false); - } - }); - - if (wasNotLaunched) { - final String installerPackageName = - packageState.getInstallSource().installerPackageName; - if (installerPackageName != null) { - notifyFirstLaunch(packageName, installerPackageName, userId); - } - } - - scheduleWritePackageRestrictions(userId); - } - } - - // If this would cause the app to leave force-stop, then also make sure to unhibernate the - // app if needed. - if (!stopped) { - mHandler.post(() -> { - AppHibernationManagerInternal ah = - mInjector.getLocalService(AppHibernationManagerInternal.class); - if (ah != null && ah.isHibernatingForUser(packageName, userId)) { - ah.setHibernatingForUser(packageName, userId, false); - ah.setHibernatingGlobally(packageName, false); - } - }); - } - } - - @Nullable - @Override - public String getInstallerPackageName(@NonNull String packageName) { - return mComputer.getInstallerPackageName(packageName); - } - - @Override - @Nullable - public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) { - return mComputer.getInstallSourceInfo(packageName); - } - - @PackageManager.EnabledState - @Override - public int getApplicationEnabledSetting(@NonNull String packageName, @UserIdInt int userId) { - return mComputer.getApplicationEnabledSetting(packageName, userId); - } - - @Override - public int getComponentEnabledSetting(@NonNull ComponentName component, int userId) { - return mComputer.getComponentEnabledSetting(component, Binder.getCallingUid(), userId); - } - - @Override - public void enterSafeMode() { - PackageManagerServiceUtils.enforceSystemOrRoot( - "Only the system can request entering safe mode"); - - if (!mSystemReady) { - mSafeMode = true; + /** + * Used by SystemServer + */ + public void waitForAppDataPrepared() { + if (mPrepareAppDataFuture == null) { + return; } + ConcurrentUtils.waitForFutureNoInterrupt(mPrepareAppDataFuture, "wait for prepareAppData"); + mPrepareAppDataFuture = null; } - @Override public void systemReady() { PackageManagerServiceUtils.enforceSystemOrRoot( "Only the system can claim the system is ready"); @@ -6270,7 +4254,7 @@ public class PackageManagerService extends IPackageManager.Stub .getUriFor(Global.ENABLE_EPHEMERAL_FEATURE), false, co, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver(android.provider.Settings.Secure - .getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_ALL); + .getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_ALL); co.onChange(true); mAppsFilter.onSystemReady(); @@ -6409,44 +4393,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - /** - * Used by SystemServer - */ - public void waitForAppDataPrepared() { - if (mPrepareAppDataFuture == null) { - return; - } - ConcurrentUtils.waitForFutureNoInterrupt(mPrepareAppDataFuture, "wait for prepareAppData"); - mPrepareAppDataFuture = null; - } - - @Override - public boolean isSafeMode() { - // allow instant applications - return mSafeMode; - } - - @Override - public boolean hasSystemUidErrors() { - // allow instant applications - return false; - } - - @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, - FileDescriptor err, String[] args, ShellCallback callback, - ResultReceiver resultReceiver) { - (new PackageManagerShellCommand(this, mContext,mDomainVerificationManager.getShell())) - .exec(this, in, out, err, args, callback, resultReceiver); - } - - @SuppressWarnings("resource") - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; - new DumpHelper(this).doDump(fd, pw, args); - } - void dumpSnapshotStats(PrintWriter pw, boolean isBrief) { if (mSnapshotStatistics == null) { return; @@ -6483,9 +4429,9 @@ public class PackageManagerService extends IPackageManager.Stub return; } for (String packageName : apkList) { - setSystemAppHiddenUntilInstalled(packageName, true); + mIPackageManager.setSystemAppHiddenUntilInstalled(packageName, true); for (UserInfo user : mInjector.getUserManagerInternal().getUsers(false)) { - setSystemAppInstallState(packageName, false, user.id); + mIPackageManager.setSystemAppInstallState(packageName, false, user.id); } } } @@ -6523,98 +4469,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public int movePackage(final String packageName, final String volumeUuid) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); - - final int callingUid = Binder.getCallingUid(); - final UserHandle user = new UserHandle(UserHandle.getUserId(callingUid)); - final int moveId = mNextMoveId.getAndIncrement(); - mHandler.post(() -> { - try { - MovePackageHelper movePackageHelper = new MovePackageHelper(this); - movePackageHelper.movePackageInternal( - packageName, volumeUuid, moveId, callingUid, user); - } catch (PackageManagerException e) { - Slog.w(TAG, "Failed to move " + packageName, e); - mMoveCallbacks.notifyStatusChanged(moveId, e.error); - } - }); - return moveId; - } - - @Override - public int movePrimaryStorage(String volumeUuid) throws RemoteException { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); - - final int realMoveId = mNextMoveId.getAndIncrement(); - final Bundle extras = new Bundle(); - extras.putString(VolumeRecord.EXTRA_FS_UUID, volumeUuid); - mMoveCallbacks.notifyCreated(realMoveId, extras); - - final IPackageMoveObserver callback = new IPackageMoveObserver.Stub() { - @Override - public void onCreated(int moveId, Bundle extras) { - // Ignored - } - - @Override - public void onStatusChanged(int moveId, int status, long estMillis) { - mMoveCallbacks.notifyStatusChanged(realMoveId, status, estMillis); - } - }; - - final StorageManager storage = mInjector.getSystemService(StorageManager.class); - storage.setPrimaryStorageUuid(volumeUuid, callback); - return realMoveId; - } - - @Override - public int getMoveStatus(int moveId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); - return mMoveCallbacks.mLastStatus.get(moveId); - } - - @Override - public void registerMoveCallback(IPackageMoveObserver callback) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); - mMoveCallbacks.register(callback); - } - - @Override - public void unregisterMoveCallback(IPackageMoveObserver callback) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); - mMoveCallbacks.unregister(callback); - } - - @Override - public boolean setInstallLocation(int loc) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, - null); - if (getInstallLocation() == loc) { - return true; - } - if (loc == InstallLocationUtils.APP_INSTALL_AUTO - || loc == InstallLocationUtils.APP_INSTALL_INTERNAL - || loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) { - android.provider.Settings.Global.putInt(mContext.getContentResolver(), - android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc); - return true; - } - return false; - } - - @Override - public int getInstallLocation() { - // allow instant app access - return android.provider.Settings.Global.getInt(mContext.getContentResolver(), - android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, - InstallLocationUtils.APP_INSTALL_AUTO); - } - /** Called by UserManagerService */ void cleanUpUser(UserManagerService userManager, @UserIdInt int userId) { synchronized (mLock) { @@ -6674,18 +4528,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, - "Only package verification agents can read the verifier device identity"); - - synchronized (mLock) { - return mSettings.getVerifierDeviceIdentityLPw(mLiveComputer); - } - } - - @Override public boolean isStorageLow() { // allow instant applications final long token = Binder.clearCallingIdentity(); @@ -6702,50 +4544,10 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public IPackageInstaller getPackageInstaller() { - // Return installer service for internal calls. - if (PackageManagerServiceUtils.isSystemOrRoot()) { - return mInstallerService; - } - // Return null for InstantApps. - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return null; - } - return mInstallerService; - } - - @Override - public IArtManager getArtManager() { - return mArtManagerService; - } - boolean userNeedsBadging(int userId) { return mUserNeedsBadging.get(userId); } - @Nullable - @Override - public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) { - return mComputer.getKeySetByAlias(packageName, alias); - } - - @Nullable - @Override - public KeySet getSigningKeySet(@NonNull String packageName) { - return mComputer.getSigningKeySet(packageName); - } - - @Override - public boolean isPackageSignedByKeySet(@NonNull String packageName, @NonNull KeySet ks) { - return mComputer.isPackageSignedByKeySet(packageName, ks); - } - - @Override - public boolean isPackageSignedByKeySetExactly(@NonNull String packageName, @NonNull KeySet ks) { - return mComputer.isPackageSignedByKeySetExactly(packageName, ks); - } - private void deletePackageIfUnused(final String packageName) { PackageStateInternal ps = getPackageStateInternal(packageName); if (ps == null) { @@ -6802,6 +4604,2512 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.canQueryPackage(callingUid, targetPackageName); } + void checkPackageStartable(@NonNull Computer snapshot, @NonNull String packageName, + @UserIdInt int userId) { + final int callingUid = Binder.getCallingUid(); + if (snapshot.getInstantAppPackageName(callingUid) != null) { + throw new SecurityException("Instant applications don't have access to this method"); + } + if (!mUserManager.exists(userId)) { + throw new SecurityException("User doesn't exist"); + } + snapshot.enforceCrossUserPermission(callingUid, userId, false, false, + "checkPackageStartable"); + switch (snapshot.getPackageStartability(mSafeMode, packageName, callingUid, userId)) { + case PACKAGE_STARTABILITY_NOT_FOUND: + throw new SecurityException("Package " + packageName + " was not found!"); + case PACKAGE_STARTABILITY_NOT_SYSTEM: + throw new SecurityException("Package " + packageName + " not a system app!"); + case PACKAGE_STARTABILITY_FROZEN: + throw new SecurityException("Package " + packageName + " is currently frozen!"); + case PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED: + throw new SecurityException("Package " + packageName + " is not encryption aware!"); + case PACKAGE_STARTABILITY_OK: + default: + } + } + + void setPackageStoppedState(@NonNull Computer snapshot, @NonNull String packageName, + boolean stopped, @UserIdInt int userId) { + if (!mUserManager.exists(userId)) return; + final int callingUid = Binder.getCallingUid(); + if (snapshot.getInstantAppPackageName(callingUid) == null) { + final int permission = mContext.checkCallingOrSelfPermission( + Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + if (!allowedByPermission + && !ArrayUtils.contains(snapshot.getPackagesForUid(callingUid), packageName)) { + throw new SecurityException( + "Permission Denial: attempt to change stopped state from pid=" + + Binder.getCallingPid() + + ", uid=" + callingUid + ", package=" + packageName); + } + snapshot.enforceCrossUserPermission(callingUid, userId, + true /* requireFullPermission */, true /* checkShell */, "stop package"); + + final PackageStateInternal packageState = + snapshot.getPackageStateInternal(packageName); + final PackageUserState packageUserState = packageState == null + ? null : packageState.getUserStateOrDefault(userId); + if (packageState != null + && !snapshot.shouldFilterApplication(packageState, callingUid, userId) + && packageUserState.isStopped() != stopped) { + boolean wasNotLaunched = packageUserState.isNotLaunched(); + commitPackageStateMutation(null, packageName, state -> { + PackageUserStateWrite userState = state.userState(userId); + userState.setStopped(stopped); + if (wasNotLaunched) { + userState.setNotLaunched(false); + } + }); + + if (wasNotLaunched) { + final String installerPackageName = + packageState.getInstallSource().installerPackageName; + if (installerPackageName != null) { + notifyFirstLaunch(packageName, installerPackageName, userId); + } + } + + scheduleWritePackageRestrictions(userId); + } + } + + // If this would cause the app to leave force-stop, then also make sure to unhibernate the + // app if needed. + if (!stopped) { + mHandler.post(() -> { + AppHibernationManagerInternal ah = + mInjector.getLocalService(AppHibernationManagerInternal.class); + if (ah != null && ah.isHibernatingForUser(packageName, userId)) { + ah.setHibernatingForUser(packageName, userId, false); + ah.setHibernatingGlobally(packageName, false); + } + }); + } + } + + public class IPackageManagerImpl extends IPackageManager.Stub { + + @Override + public boolean activitySupportsIntent(ComponentName component, Intent intent, + String resolvedType) { + return mComputer.activitySupportsIntent(mResolveComponentName, component, intent, + resolvedType); + } + + @Override + public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage, + int sourceUserId, int targetUserId, int flags) { + PackageManagerService.this.addCrossProfileIntentFilter( + new WatchedIntentFilter(intentFilter), ownerPackage, sourceUserId, targetUserId, + flags); + } + + // NOTE: Can't remove due to unsupported app usage + @Override + public boolean addPermission(PermissionInfo info) { + // Because this is accessed via the package manager service AIDL, + // go through the permission manager service AIDL + return mContext.getSystemService(PermissionManager.class).addPermission(info, false); + } + + // NOTE: Can't remove due to unsupported app usage + @Override + public boolean addPermissionAsync(PermissionInfo info) { + // Because this is accessed via the package manager service AIDL, + // go through the permission manager service AIDL + return mContext.getSystemService(PermissionManager.class).addPermission(info, true); + } + + @Override + public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity, + int userId) { + mPreferredActivityHelper.addPersistentPreferredActivity(new WatchedIntentFilter(filter), + activity, userId); + } + + @Override + public void addPreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, int userId, boolean removeExisting) { + mPreferredActivityHelper.addPreferredActivity(new WatchedIntentFilter(filter), match, + set, activity, true, userId, "Adding preferred", removeExisting); + } + + /* + * Returns if intent can be forwarded from the sourceUserId to the targetUserId + */ + @Override + public boolean canForwardTo(@NonNull Intent intent, @Nullable String resolvedType, + @UserIdInt int sourceUserId, @UserIdInt int targetUserId) { + return mComputer.canForwardTo(intent, resolvedType, sourceUserId, targetUserId); + } + + @Override + public boolean canRequestPackageInstalls(String packageName, int userId) { + return mComputer.canRequestPackageInstalls(packageName, Binder.getCallingUid(), userId, + true /* throwIfPermNotDeclared*/); + } + + @Override + public String[] canonicalToCurrentPackageNames(String[] names) { + return mComputer.canonicalToCurrentPackageNames(names); + } + + @Override + public void checkPackageStartable(String packageName, int userId) { + PackageManagerService.this + .checkPackageStartable(snapshotComputer(), packageName, userId); + } + + // NOTE: Can't remove due to unsupported app usage + @Override + public int checkPermission(String permName, String pkgName, int userId) { + return PackageManagerService.this.checkPermission(permName, pkgName, userId); + } + + @Override + public int checkSignatures(@NonNull String pkg1, @NonNull String pkg2) { + return mComputer.checkSignatures(pkg1, pkg2); + } + + @Override + public int checkUidPermission(String permName, int uid) { + return mComputer.checkUidPermission(permName, uid); + } + + @Override + public int checkUidSignatures(int uid1, int uid2) { + return mComputer.checkUidSignatures(uid1, uid2); + } + + @Override + public void clearApplicationProfileData(String packageName) { + PackageManagerServiceUtils.enforceSystemOrRoot( + "Only the system can clear all profile data"); + + final AndroidPackage pkg = getPackage(packageName); + try (PackageFreezer ignored = freezePackage(packageName, "clearApplicationProfileData")) { + synchronized (mInstallLock) { + mAppDataHelper.clearAppProfilesLIF(pkg); + } + } + } + + @Override + public void clearApplicationUserData(final String packageName, + final IPackageDataObserver observer, final int userId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CLEAR_APP_USER_DATA, null); + + final int callingUid = Binder.getCallingUid(); + enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, + false /* checkShell */, "clear application data"); + + if (mComputer.getPackageStateFiltered(packageName, callingUid, userId) == null) { + if (observer != null) { + mHandler.post(() -> { + try { + observer.onRemoveCompleted(packageName, false); + } catch (RemoteException e) { + Log.i(TAG, "Observer no longer exists."); + } + }); + } + return; + } + if (mProtectedPackages.isPackageDataProtected(userId, packageName)) { + throw new SecurityException("Cannot clear data for a protected package: " + + packageName); + } + + // Queue up an async operation since the package deletion may take a little while. + mHandler.post(new Runnable() { + public void run() { + mHandler.removeCallbacks(this); + final boolean succeeded; + try (PackageFreezer freezer = freezePackage(packageName, + "clearApplicationUserData")) { + synchronized (mInstallLock) { + succeeded = clearApplicationUserDataLIF(packageName, userId); + } + mInstantAppRegistry.deleteInstantApplicationMetadata(packageName, userId); + synchronized (mLock) { + if (succeeded) { + resetComponentEnabledSettingsIfNeededLPw(packageName, userId); + } + } + } + if (succeeded) { + // invoke DeviceStorageMonitor's update method to clear any notifications + DeviceStorageMonitorInternal dsm = LocalServices + .getService(DeviceStorageMonitorInternal.class); + if (dsm != null) { + dsm.checkMemory(); + } + if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) + == PERMISSION_GRANTED) { + unsuspendForSuspendingPackage(snapshotComputer(), packageName, userId); + removeAllDistractingPackageRestrictions(userId); + flushPackageRestrictionsAsUserInternalLocked(userId); + } + } + if (observer != null) { + try { + observer.onRemoveCompleted(packageName, succeeded); + } catch (RemoteException e) { + Log.i(TAG, "Observer no longer exists."); + } + } //end if observer + } //end run + }); + } + + @Override + public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + final int callingUid = Binder.getCallingUid(); + enforceOwnerRights(ownerPackage, callingUid); + PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(), + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId); + synchronized (mLock) { + CrossProfileIntentResolver resolver = + mSettings.editCrossProfileIntentResolverLPw(sourceUserId); + ArraySet<CrossProfileIntentFilter> set = + new ArraySet<>(resolver.filterSet()); + for (CrossProfileIntentFilter filter : set) { + if (filter.getOwnerPackage().equals(ownerPackage)) { + resolver.removeFilter(filter); + } + } + } + scheduleWritePackageRestrictions(sourceUserId); + } + + @Override + public void clearPackagePersistentPreferredActivities(String packageName, int userId) { + mPreferredActivityHelper.clearPackagePersistentPreferredActivities(packageName, userId); + } + + @Override + public void clearPackagePreferredActivities(String packageName) { + mPreferredActivityHelper.clearPackagePreferredActivities(packageName); + } + + @Override + public String[] currentToCanonicalPackageNames(String[] names) { + return mComputer.currentToCanonicalPackageNames(names); + } + + @Override + public void deleteApplicationCacheFiles(final String packageName, + final IPackageDataObserver observer) { + final int userId = UserHandle.getCallingUserId(); + deleteApplicationCacheFilesAsUser(packageName, userId, observer); + } + + @Override + public void deleteApplicationCacheFilesAsUser(final String packageName, final int userId, + final IPackageDataObserver observer) { + final int callingUid = Binder.getCallingUid(); + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.INTERNAL_DELETE_CACHE_FILES) + != PackageManager.PERMISSION_GRANTED) { + // If the caller has the old delete cache permission, silently ignore. Else throw. + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.DELETE_CACHE_FILES) + == PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Calling uid " + callingUid + " does not have " + + android.Manifest.permission.INTERNAL_DELETE_CACHE_FILES + + ", silently ignoring"); + return; + } + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERNAL_DELETE_CACHE_FILES, null); + } + enforceCrossUserPermission(callingUid, userId, /* requireFullPermission= */ true, + /* checkShell= */ false, "delete application cache files"); + final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.ACCESS_INSTANT_APPS); + + final AndroidPackage pkg = getPackage(packageName); + + // Queue up an async operation since the package deletion may take a little while. + mHandler.post(() -> { + final PackageStateInternal ps = + pkg == null ? null : getPackageStateInternal(pkg.getPackageName()); + boolean doClearData = true; + if (ps != null) { + final boolean targetIsInstantApp = + ps.getUserStateOrDefault(UserHandle.getUserId(callingUid)).isInstantApp(); + doClearData = !targetIsInstantApp + || hasAccessInstantApps == PackageManager.PERMISSION_GRANTED; + } + if (doClearData) { + synchronized (mInstallLock) { + final int flags = FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL; + // We're only clearing cache files, so we don't care if the + // app is unfrozen and still able to run + mAppDataHelper.clearAppDataLIF(pkg, userId, + flags | Installer.FLAG_CLEAR_CACHE_ONLY); + mAppDataHelper.clearAppDataLIF(pkg, userId, + flags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); + } + } + if (observer != null) { + try { + observer.onRemoveCompleted(packageName, true); + } catch (RemoteException e) { + Log.i(TAG, "Observer no longer exists."); + } + } + }); + } + + @Override + public void deleteExistingPackageAsUser(VersionedPackage versionedPackage, + final IPackageDeleteObserver2 observer, final int userId) { + PackageManagerService.this.deleteExistingPackageAsUser(versionedPackage, observer, + userId); + } + + @Override + public void deletePackageAsUser(String packageName, int versionCode, + IPackageDeleteObserver observer, int userId, int flags) { + deletePackageVersioned(new VersionedPackage(packageName, versionCode), + new PackageManager.LegacyPackageDeleteObserver(observer).getBinder(), userId, flags); + } + + @Override + public void deletePackageVersioned(VersionedPackage versionedPackage, + final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) { + PackageManagerService.this.deletePackageVersioned(versionedPackage, observer, + userId, deleteFlags); + } + + @Override + public void deletePreloadsFileCache() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CLEAR_APP_CACHE, + "deletePreloadsFileCache"); + File dir = Environment.getDataPreloadsFileCacheDirectory(); + Slog.i(PackageManagerService.TAG, "Deleting preloaded file cache " + dir); + FileUtils.deleteContents(dir); + } + + @Override + public void dumpProfiles(String packageName) { + /* Only the shell, root, or the app user should be able to dump profiles. */ + final int callingUid = Binder.getCallingUid(); + final String[] callerPackageNames = getPackagesForUid(callingUid); + if (callingUid != Process.SHELL_UID + && callingUid != Process.ROOT_UID + && !ArrayUtils.contains(callerPackageNames, packageName)) { + throw new SecurityException("dumpProfiles"); + } + + AndroidPackage pkg = mComputer.getPackage(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + synchronized (mInstallLock) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles"); + mArtManagerService.dumpProfiles(pkg); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + + @Override + public void enterSafeMode() { + PackageManagerServiceUtils.enforceSystemOrRoot( + "Only the system can request entering safe mode"); + + if (!mSystemReady) { + mSafeMode = true; + } + } + + @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, + long millisecondsToDelay) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.PACKAGE_VERIFICATION_AGENT, + "Only package verification agents can extend verification timeouts"); + final int callingUid = Binder.getCallingUid(); + + mHandler.post(() -> { + final PackageVerificationState state = mPendingVerification.get(id); + final PackageVerificationResponse response = new PackageVerificationResponse( + verificationCodeAtTimeout, callingUid); + + long delay = millisecondsToDelay; + if (delay > PackageManager.MAXIMUM_VERIFICATION_TIMEOUT) { + delay = PackageManager.MAXIMUM_VERIFICATION_TIMEOUT; + } + if (delay < 0) { + delay = 0; + } + + if ((state != null) && !state.timeoutExtended()) { + state.extendTimeout(); + + final Message msg = mHandler.obtainMessage(PackageManagerService.PACKAGE_VERIFIED); + msg.arg1 = id; + msg.obj = response; + mHandler.sendMessageDelayed(msg, delay); + } + }); + } + + @Override + public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) { + return mPreferredActivityHelper.findPersistentPreferredActivity(intent, userId); + } + + @Override + public void finishPackageInstall(int token, boolean didLaunch) { + PackageManagerServiceUtils.enforceSystemOrRoot( + "Only the system is allowed to finish installs"); + + if (PackageManagerService.DEBUG_INSTALL) { + Slog.v(PackageManagerService.TAG, "BM finishing package install for " + token); + } + Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "restore", token); + + final Message msg = mHandler.obtainMessage(PackageManagerService.POST_INSTALL, token, didLaunch ? 1 : 0); + mHandler.sendMessage(msg); + } + + @WorkerThread + @Override + public void flushPackageRestrictionsAsUser(int userId) { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return; + } + if (!mUserManager.exists(userId)) { + return; + } + enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/, + false /* checkShell */, "flushPackageRestrictions"); + synchronized (mLock) { + flushPackageRestrictionsAsUserInternalLocked(userId); + } + } + + @Override + public void forceDexOpt(String packageName) { + mDexOptHelper.forceDexOpt(packageName); + } + + + @Override + public void freeStorage(final String volumeUuid, final long freeStorageSize, + final @StorageManager.AllocateFlags int flags, final IntentSender pi) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CLEAR_APP_CACHE, TAG); + mHandler.post(() -> { + boolean success = false; + try { + PackageManagerService.this.freeStorage(volumeUuid, freeStorageSize, flags); + success = true; + } catch (IOException e) { + Slog.w(TAG, e); + } + if (pi != null) { + try { + pi.sendIntent(null, success ? 1 : 0, null, null, null); + } catch (SendIntentException e) { + Slog.w(TAG, e); + } + } + }); + } + + @Override + public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize, + final @StorageManager.AllocateFlags int flags, final IPackageDataObserver observer) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CLEAR_APP_CACHE, null); + mHandler.post(() -> { + boolean success = false; + try { + PackageManagerService.this.freeStorage(volumeUuid, freeStorageSize, flags); + success = true; + } catch (IOException e) { + Slog.w(PackageManagerService.TAG, e); + } + if (observer != null) { + try { + observer.onRemoveCompleted(null, success); + } catch (RemoteException e) { + Slog.w(PackageManagerService.TAG, e); + } + } + }); + } + + @Override + public ActivityInfo getActivityInfo(ComponentName component, + @PackageManager.ComponentInfoFlagsBits long flags, int userId) { + return mComputer.getActivityInfo(component, flags, userId); + } + + @NonNull + @Override + public ParceledListSlice<IntentFilter> getAllIntentFilters(@NonNull String packageName) { + return mComputer.getAllIntentFilters(packageName); + } + + @Override + public List<String> getAllPackages() { + return mComputer.getAllPackages(); + } + + // NOTE: Can't remove due to unsupported app usage + @NonNull + @Override + public String[] getAppOpPermissionPackages(@NonNull String permissionName) { + return mComputer.getAppOpPermissionPackages(permissionName); + } + + @Override + public String getAppPredictionServicePackageName() { + return ensureSystemPackageName( + getPackageFromComponentString(R.string.config_defaultAppPredictionService)); + } + + @PackageManager.EnabledState + @Override + public int getApplicationEnabledSetting(@NonNull String packageName, @UserIdInt int userId) { + return mComputer.getApplicationEnabledSetting(packageName, userId); + } + + /** + * Returns true if application is not found or there was an error. Otherwise it returns + * the hidden state of the package for the given user. + */ + @Override + public boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, + @UserIdInt int userId) { + return mComputer.getApplicationHiddenSettingAsUser(packageName, userId); + } + + @Override + public ApplicationInfo getApplicationInfo(String packageName, + @PackageManager.ApplicationInfoFlagsBits long flags, int userId) { + return mComputer.getApplicationInfo(packageName, flags, userId); + } + + @Override + public IArtManager getArtManager() { + return mArtManagerService; + } + + @Override + public @Nullable String getAttentionServicePackageName() { + return ensureSystemPackageName( + getPackageFromComponentString(R.string.config_defaultAttentionService)); + } + + @Override + public boolean getBlockUninstallForUser(@NonNull String packageName, @UserIdInt int userId) { + return mComputer.getBlockUninstallForUser(packageName, userId); + } + + @Override + public ChangedPackages getChangedPackages(int sequenceNumber, int userId) { + final int callingUid = Binder.getCallingUid(); + if (getInstantAppPackageName(callingUid) != null) { + return null; + } + if (!mUserManager.exists(userId)) { + return null; + } + enforceCrossUserPermission(callingUid, userId, false, false, "getChangedPackages"); + final ChangedPackages changedPackages = mChangedPackagesTracker.getChangedPackages( + sequenceNumber, userId); + + if (changedPackages != null) { + final List<String> packageNames = changedPackages.getPackageNames(); + for (int index = packageNames.size() - 1; index >= 0; index--) { + // Filter out the changes if the calling package should not be able to see it. + final PackageSetting ps = mSettings.getPackageLPr(packageNames.get(index)); + if (shouldFilterApplication(ps, callingUid, userId)) { + packageNames.remove(index); + } + } + } + + return changedPackages; + } + + @Override + public int getComponentEnabledSetting(@NonNull ComponentName component, int userId) { + return mComputer.getComponentEnabledSetting(component, Binder.getCallingUid(), userId); + } + + @Override + public String getContentCaptureServicePackageName() { + return ensureSystemPackageName( + getPackageFromComponentString(R.string.config_defaultContentCaptureService)); + } + + @Nullable + @Override + public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( + @NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags, + @NonNull int userId) { + return mComputer.getDeclaredSharedLibraries(packageName, flags, userId); + } + + /** + * Non-Binder method, support for the backup/restore mechanism: write the + * default browser (etc) settings in its canonical XML format. Returns the default + * browser XML representation as a byte array, or null if there is none. + */ + @Override + public byte[] getDefaultAppsBackup(int userId) { + return mPreferredActivityHelper.getDefaultAppsBackup(userId); + } + + @Override + public String getDefaultTextClassifierPackageName() { + return ensureSystemPackageName( + mContext.getString(R.string.config_servicesExtensionPackage)); + } + + @Override + public byte[] getDomainVerificationBackup(int userId) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system may call getDomainVerificationBackup()"); + } + + try { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + TypedXmlSerializer serializer = Xml.resolveSerializer(output); + mDomainVerificationManager.writeSettings(snapshotComputer(), serializer, true, + userId); + return output.toByteArray(); + } + } catch (Exception e) { + if (PackageManagerService.DEBUG_BACKUP) { + Slog.e(PackageManagerService.TAG, "Unable to write domain verification for backup", e); + } + return null; + } + } + + @Override + public int getFlagsForUid(int uid) { + return mComputer.getFlagsForUid(uid); + } + + @Nullable + @Override + public CharSequence getHarmfulAppWarning(@NonNull String packageName, @UserIdInt int userId) { + return mComputer.getHarmfulAppWarning(packageName, userId); + } + + @Override + public IBinder getHoldLockToken() { + if (!Build.IS_DEBUGGABLE) { + throw new SecurityException("getHoldLockToken requires a debuggable build"); + } + + mContext.enforceCallingPermission( + Manifest.permission.INJECT_EVENTS, + "getHoldLockToken requires INJECT_EVENTS permission"); + + final Binder token = new Binder(); + token.attachInterface(this, "holdLock:" + Binder.getCallingUid()); + return token; + } + + @Override + public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return null; + } + return getHomeActivitiesAsUser(allHomeCandidates, UserHandle.getCallingUserId()); + } + + public String getIncidentReportApproverPackageName() { + return ensureSystemPackageName(mContext.getString( + R.string.config_incidentReportApproverPackage)); + } + + @Override + public int getInstallLocation() { + // allow instant app access + return android.provider.Settings.Global.getInt(mContext.getContentResolver(), + android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, + InstallLocationUtils.APP_INSTALL_AUTO); + } + + @PackageManager.InstallReason + @Override + public int getInstallReason(@NonNull String packageName, @UserIdInt int userId) { + return mComputer.getInstallReason(packageName, userId); + } + + @Override + @Nullable + public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) { + return mComputer.getInstallSourceInfo(packageName); + } + + @Override + public ParceledListSlice<ApplicationInfo> getInstalledApplications( + @PackageManager.ApplicationInfoFlagsBits long flags, int userId) { + final int callingUid = Binder.getCallingUid(); + return new ParceledListSlice<>( + mComputer.getInstalledApplications(flags, userId, callingUid)); + } + + @Override + public List<ModuleInfo> getInstalledModules(int flags) { + return mModuleInfoProvider.getInstalledModules(flags); + } + + @Override + public ParceledListSlice<PackageInfo> getInstalledPackages( + @PackageManager.PackageInfoFlagsBits long flags, int userId) { + return mComputer.getInstalledPackages(flags, userId); + } + + @Nullable + @Override + public String getInstallerPackageName(@NonNull String packageName) { + return mComputer.getInstallerPackageName(packageName); + } + + @Override + public String getInstantAppAndroidId(String packageName, int userId) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS, + "getInstantAppAndroidId"); + enforceCrossUserPermission(Binder.getCallingUid(), userId, + true /* requireFullPermission */, false /* checkShell */, + "getInstantAppAndroidId"); + // Make sure the target is an Instant App. + if (!isInstantApp(packageName, userId)) { + return null; + } + return mInstantAppRegistry.getInstantAppAndroidId(packageName, userId); + } + + @Override + public byte[] getInstantAppCookie(String packageName, int userId) { + if (HIDE_EPHEMERAL_APIS) { + return null; + } + + enforceCrossUserPermission(Binder.getCallingUid(), userId, + true /* requireFullPermission */, false /* checkShell */, + "getInstantAppCookie"); + if (!isCallerSameApp(packageName, Binder.getCallingUid())) { + return null; + } + PackageStateInternal packageState = getPackageStateInternal(packageName); + if (packageState == null || packageState.getPkg() == null) { + return null; + } + return mInstantAppRegistry.getInstantAppCookie(packageState.getPkg(), userId); + } + + @Override + public Bitmap getInstantAppIcon(String packageName, int userId) { + if (HIDE_EPHEMERAL_APIS) { + return null; + } + + if (!canViewInstantApps(Binder.getCallingUid(), userId)) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS, + "getInstantAppIcon"); + } + enforceCrossUserPermission(Binder.getCallingUid(), userId, + true /* requireFullPermission */, false /* checkShell */, + "getInstantAppIcon"); + + return mInstantAppRegistry.getInstantAppIcon(packageName, userId); + } + + @Override + public ComponentName getInstantAppInstallerComponent() { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return null; + } + return mInstantAppInstallerActivity == null + ? null : mInstantAppInstallerActivity.getComponentName(); + } + + @Override + public @Nullable ComponentName getInstantAppResolverComponent() { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return null; + } + return getInstantAppResolver(); + } + + @Override + public ComponentName getInstantAppResolverSettingsComponent() { + return mInstantAppResolverSettingsComponent; + } + + @Override + public ParceledListSlice<InstantAppInfo> getInstantApps(int userId) { + if (PackageManagerService.HIDE_EPHEMERAL_APIS) { + return null; + } + if (!canViewInstantApps(Binder.getCallingUid(), userId)) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS, + "getEphemeralApplications"); + } + enforceCrossUserPermission(Binder.getCallingUid(), userId, + true /* requireFullPermission */, false /* checkShell */, + "getEphemeralApplications"); + + Computer computer = snapshotComputer(); + List<InstantAppInfo> instantApps = mInstantAppRegistry.getInstantApps(computer, userId); + if (instantApps != null) { + return new ParceledListSlice<>(instantApps); + } + return null; + } + + @Nullable + @Override + public InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName component, int flags) { + return mComputer.getInstrumentationInfo(component, flags); + } + + @Deprecated + @Override + public @NonNull ParceledListSlice<IntentFilterVerificationInfo> getIntentFilterVerifications( + String packageName) { + return ParceledListSlice.emptyList(); + } + + @Deprecated + @Override + public int getIntentVerificationStatus(String packageName, int userId) { + return mDomainVerificationManager.getLegacyState(packageName, userId); + } + + @Nullable + @Override + public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) { + return mComputer.getKeySetByAlias(packageName, alias); + } + + @Override + public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) { + return mPreferredActivityHelper.getLastChosenActivity(intent, resolvedType, flags); + } + + @Override + public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage, + String featureId, int userId) throws RemoteException { + return mResolveIntentHelper.getLaunchIntentSenderForPackage(snapshotComputer(), + packageName, callingPackage, featureId, userId); + } + + @Override + public List<String> getMimeGroup(String packageName, String mimeGroup) { + enforceOwnerRights(packageName, Binder.getCallingUid()); + return getMimeGroupInternal(packageName, mimeGroup); + } + + @Override + public ModuleInfo getModuleInfo(String packageName, @PackageManager.ModuleInfoFlags int flags) { + return PackageManagerService.this.getModuleInfo(packageName, flags); + } + + @Override + public int getMoveStatus(int moveId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + return mMoveCallbacks.mLastStatus.get(moveId); + } + + @Nullable + @Override + public String getNameForUid(int uid) { + return mComputer.getNameForUid(uid); + } + + @Nullable + @Override + public String[] getNamesForUids(@NonNull int[] uids) { + return mComputer.getNamesForUids(uids); + } + + @Override + public int[] getPackageGids(String packageName, @PackageManager.PackageInfoFlagsBits long flags, + int userId) { + return mComputer.getPackageGids(packageName, flags, userId); + } + + @Override + public PackageInfo getPackageInfo(String packageName, + @PackageManager.PackageInfoFlagsBits long flags, int userId) { + return mComputer.getPackageInfo(packageName, flags, userId); + } + + @Override + public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, + @PackageManager.PackageInfoFlagsBits long flags, int userId) { + return mComputer.getPackageInfoInternal(versionedPackage.getPackageName(), + versionedPackage.getLongVersionCode(), flags, Binder.getCallingUid(), userId); + } + + @Override + public IPackageInstaller getPackageInstaller() { + // Return installer service for internal calls. + if (PackageManagerServiceUtils.isSystemOrRoot()) { + return mInstallerService; + } + // Return null for InstantApps. + if (snapshotComputer().getInstantAppPackageName(Binder.getCallingUid()) != null) { + return null; + } + return mInstallerService; + } + + @Override + public void getPackageSizeInfo(final String packageName, int userId, + final IPackageStatsObserver observer) { + throw new UnsupportedOperationException( + "Shame on you for calling the hidden API getPackageSizeInfo(). Shame!"); + } + + @Override + public int getPackageUid(@NonNull String packageName, + @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) { + return mComputer.getPackageUid(packageName, flags, userId); + } + + /** + * <em>IMPORTANT:</em> Not all packages returned by this method may be known + * to the system. There are two conditions in which this may occur: + * <ol> + * <li>The package is on adoptable storage and the device has been removed</li> + * <li>The package is being removed and the internal structures are partially updated</li> + * </ol> + * The second is an artifact of the current data structures and should be fixed. See + * b/111075456 for one such instance. + * This binder API is cached. If the algorithm in this method changes, + * or if the underlying objecs (as returned by getSettingLPr()) change + * then the logic that invalidates the cache must be revisited. See + * calls to invalidateGetPackagesForUidCache() to locate the points at + * which the cache is invalidated. + */ + @Override + public String[] getPackagesForUid(int uid) { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(uid); + mComputer.enforceCrossUserOrProfilePermission(callingUid, userId, + /* requireFullPermission */ false, + /* checkShell */ false, "getPackagesForUid"); + return mComputer.getPackagesForUid(uid); + } + + @Override + public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions( + @NonNull String[] permissions, @PackageManager.PackageInfoFlagsBits long flags, + @UserIdInt int userId) { + return mComputer.getPackagesHoldingPermissions(permissions, flags, userId); + } + + @Override + public String getPermissionControllerPackageName() { + final int callingUid = Binder.getCallingUid(); + if (mComputer.getPackageStateFiltered(mRequiredPermissionControllerPackage, + callingUid, UserHandle.getUserId(callingUid)) != null) { + return mRequiredPermissionControllerPackage; + } + + throw new IllegalStateException("PermissionController is not found"); + } + + // NOTE: Can't remove due to unsupported app usage + @Override + public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) { + return PackageManagerService.this.getPermissionGroupInfo(groupName, flags); + } + + @Override + public @NonNull ParceledListSlice<ApplicationInfo> getPersistentApplications(int flags) { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return ParceledListSlice.emptyList(); + } + return new ParceledListSlice<>(mComputer.getPersistentApplications(mSafeMode, flags)); + } + + @Override + public int getPreferredActivities(List<IntentFilter> outFilters, + List<ComponentName> outActivities, String packageName) { + return mPreferredActivityHelper.getPreferredActivities(outFilters, outActivities, + packageName, snapshotComputer()); + } + + /** + * Non-Binder method, support for the backup/restore mechanism: write the + * full set of preferred activities in its canonical XML format. Returns the + * XML output as a byte array, or null if there is none. + */ + @Override + public byte[] getPreferredActivityBackup(int userId) { + return mPreferredActivityHelper.getPreferredActivityBackup(userId); + } + + @Override + public int getPrivateFlagsForUid(int uid) { + return mComputer.getPrivateFlagsForUid(uid); + } + + @Override + public PackageManager.Property getProperty(String propertyName, String packageName, String className) { + Objects.requireNonNull(propertyName); + Objects.requireNonNull(packageName); + PackageStateInternal packageState = mComputer.getPackageStateFiltered(packageName, + Binder.getCallingUid(), UserHandle.getCallingUserId()); + if (packageState == null) { + return null; + } + return mPackageProperty.getProperty(propertyName, packageName, className); + } + + @Nullable + @Override + public ProviderInfo getProviderInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) { + return mComputer.getProviderInfo(component, flags, userId); + } + + @Override + public ActivityInfo getReceiverInfo(ComponentName component, + @PackageManager.ComponentInfoFlagsBits long flags, int userId) { + return mComputer.getReceiverInfo(component, flags, userId); + } + + @Override + public @Nullable String getRotationResolverPackageName() { + return ensureSystemPackageName( + getPackageFromComponentString(R.string.config_defaultRotationResolverService)); + } + + @Override + public int getRuntimePermissionsVersion(@UserIdInt int userId) { + Preconditions.checkArgumentNonnegative(userId); + enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions( + "getRuntimePermissionVersion"); + return mSettings.getDefaultRuntimePermissionsVersion(userId); + } + + @Nullable + @Override + public ServiceInfo getServiceInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) { + return mComputer.getServiceInfo(component, flags, userId); + } + + @Override + public @NonNull String getServicesSystemSharedLibraryPackageName() { + return mServicesExtensionPackageName; + } + + @Override + public String getSetupWizardPackageName() { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Non-system caller"); + } + return mPmInternal.getSetupWizardPackageName(); + } + + @Override + public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(String packageName, + @PackageManager.PackageInfoFlagsBits long flags, int userId) { + return mComputer.getSharedLibraries(packageName, flags, userId); + } + + @Override + public @NonNull String getSharedSystemSharedLibraryPackageName() { + return mSharedSystemSharedLibraryPackageName; + } + + @Nullable + @Override + public KeySet getSigningKeySet(@NonNull String packageName) { + return mComputer.getSigningKeySet(packageName); + } + + @Override + public String getSplashScreenTheme(@NonNull String packageName, int userId) { + PackageStateInternal packageState = + getPackageStateInstalledFiltered(packageName, Binder.getCallingUid(), userId); + return packageState == null ? null + : packageState.getUserStateOrDefault(userId).getSplashScreenTheme(); + } + + @Override + public String getSdkSandboxPackageName() { + return mRequiredSdkSandboxPackage; + } + + @Override + public Bundle getSuspendedPackageAppExtras(String packageName, int userId) { + final int callingUid = Binder.getCallingUid(); + if (getPackageUid(packageName, 0, userId) != callingUid) { + throw new SecurityException("Calling package " + packageName + + " does not belong to calling uid " + callingUid); + } + return mSuspendPackageHelper.getSuspendedPackageAppExtras( + packageName, userId, callingUid); + } + + @Override + public @NonNull ParceledListSlice<FeatureInfo> getSystemAvailableFeatures() { + // allow instant applications + ArrayList<FeatureInfo> res; + synchronized (mAvailableFeatures) { + res = new ArrayList<>(mAvailableFeatures.size() + 1); + res.addAll(mAvailableFeatures.values()); + } + final FeatureInfo fi = new FeatureInfo(); + fi.reqGlEsVersion = SystemProperties.getInt("ro.opengles.version", + FeatureInfo.GL_ES_VERSION_UNDEFINED); + res.add(fi); + + return new ParceledListSlice<>(res); + } + + @Override + public String getSystemCaptionsServicePackageName() { + return ensureSystemPackageName( + getPackageFromComponentString(R.string.config_defaultSystemCaptionsService)); + } + + @Nullable + @Override + public String[] getSystemSharedLibraryNames() { + return mComputer.getSystemSharedLibraryNames(); + } + + @Override + public String getSystemTextClassifierPackageName() { + return ensureSystemPackageName( + mContext.getString(R.string.config_defaultTextClassifierPackage)); + } + + @Override + public int getTargetSdkVersion(@NonNull String packageName) { + return mComputer.getTargetSdkVersion(packageName); + } + + @Override + public int getUidForSharedUser(@NonNull String sharedUserName) { + return mComputer.getUidForSharedUser(sharedUserName); + } + + @Override + public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) { + Objects.requireNonNull(packageNames, "packageNames cannot be null"); + mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, + "getUnsuspendablePackagesForUser"); + final int callingUid = Binder.getCallingUid(); + if (UserHandle.getUserId(callingUid) != userId) { + throw new SecurityException("Calling uid " + callingUid + + " cannot query getUnsuspendablePackagesForUser for user " + userId); + } + return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(), + packageNames, userId, callingUid); + } + + @Override + public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.PACKAGE_VERIFICATION_AGENT, + "Only package verification agents can read the verifier device identity"); + + synchronized (mLock) { + return mSettings.getVerifierDeviceIdentityLPw(mLiveComputer); + } + } + + @Override + public String getWellbeingPackageName() { + final long identity = Binder.clearCallingIdentity(); + try { + return CollectionUtils.firstOrNull( + mContext.getSystemService(RoleManager.class).getRoleHolders( + RoleManager.ROLE_SYSTEM_WELLBEING)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void grantImplicitAccess(int recipientUid, @NonNull String visibleAuthority) { + final Computer snapshot = snapshotComputer(); + final int recipientUserId = UserHandle.getUserId(recipientUid); + final ProviderInfo providerInfo = + snapshot.getGrantImplicitAccessProviderInfo(recipientUid, visibleAuthority); + if (providerInfo == null) { + return; + } + int visibleUid = providerInfo.applicationInfo.uid; + PackageManagerService.this.grantImplicitAccess(snapshot, recipientUserId, + null /*Intent*/, UserHandle.getAppId(recipientUid), visibleUid, + false /*direct*/, false /* retainOnUpdate */); + } + + // NOTE: Can't remove due to unsupported app usage + @Override + public void grantRuntimePermission(String packageName, String permName, final int userId) { + // Because this is accessed via the package manager service AIDL, + // go through the permission manager service AIDL + mContext.getSystemService(PermissionManager.class) + .grantRuntimePermission(packageName, permName, UserHandle.of(userId)); + } + + @Override + public boolean hasSigningCertificate(@NonNull String packageName, @NonNull byte[] certificate, + @PackageManager.CertificateInputType int type) { + return mComputer.hasSigningCertificate(packageName, certificate, type); + } + + @Override + public boolean hasSystemFeature(String name, int version) { + return PackageManagerService.this.hasSystemFeature(name, version); + } + + @Override + public boolean hasSystemUidErrors() { + // allow instant applications + return false; + } + + @Override + public boolean hasUidSigningCertificate(int uid, @NonNull byte[] certificate, + @PackageManager.CertificateInputType int type) { + return mComputer.hasUidSigningCertificate(uid, certificate, type); + } + + @Override + public void holdLock(IBinder token, int durationMs) { + mTestUtilityService.verifyHoldLockToken(token); + + synchronized (mLock) { + SystemClock.sleep(durationMs); + } + } + + /** + * @hide + */ + @Override + public int installExistingPackageAsUser(String packageName, int userId, int installFlags, + int installReason, List<String> whiteListedPermissions) { + return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags, + installReason, whiteListedPermissions, null); + } + + @Override + public boolean isAutoRevokeWhitelisted(String packageName) { + int mode = mInjector.getSystemService(AppOpsManager.class).checkOpNoThrow( + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + Binder.getCallingUid(), packageName); + return mode == MODE_IGNORED; + } + + @Override + public boolean isDeviceUpgrading() { + return PackageManagerService.this.isDeviceUpgrading(); + } + + @Override + public boolean isFirstBoot() { + return PackageManagerService.this.isFirstBoot(); + } + + @Override + public boolean isInstantApp(String packageName, int userId) { + return mComputer.isInstantApp(packageName, userId); + } + + @Override + public boolean isOnlyCoreApps() { + return PackageManagerService.this.isOnlyCoreApps(); + } + + @Override + public boolean isPackageAvailable(String packageName, int userId) { + return mComputer.isPackageAvailable(packageName, userId); + } + + @Override + public boolean isPackageDeviceAdminOnAnyUser(String packageName) { + return PackageManagerService.this.isPackageDeviceAdminOnAnyUser(packageName); + } + + @Override + public boolean isPackageSignedByKeySet(@NonNull String packageName, @NonNull KeySet ks) { + return mComputer.isPackageSignedByKeySet(packageName, ks); + } + + @Override + public boolean isPackageSignedByKeySetExactly(@NonNull String packageName, @NonNull KeySet ks) { + return mComputer.isPackageSignedByKeySetExactly(packageName, ks); + } + + @Override + public boolean isPackageStateProtected(@NonNull String packageName, @UserIdInt int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingAppId = UserHandle.getAppId(callingUid); + + enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, + true /*checkShell*/, "isPackageStateProtected"); + + if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID + && checkUidPermission(MANAGE_DEVICE_ADMINS, callingUid) != PERMISSION_GRANTED) { + throw new SecurityException("Caller must have the " + + MANAGE_DEVICE_ADMINS + " permission."); + } + + return mProtectedPackages.isPackageStateProtected(userId, packageName); + } + + @Override + public boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId) { + return mComputer.isPackageSuspendedForUser(packageName, userId); + } + + @Override + public boolean isProtectedBroadcast(String actionName) { + if (actionName != null) { + // TODO: remove these terrible hacks + if (actionName.startsWith("android.net.netmon.lingerExpired") + || actionName.startsWith("com.android.server.sip.SipWakeupTimer") + || actionName.startsWith("com.android.internal.telephony.data-reconnect") + || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) { + return true; + } + } + // allow instant applications + synchronized (mProtectedBroadcasts) { + return mProtectedBroadcasts.contains(actionName); + } + } + + @Override + public boolean isSafeMode() { + // allow instant applications + return mSafeMode; + } + + @Override + public boolean isStorageLow() { + return PackageManagerService.this.isStorageLow(); + } + + @Override + public boolean isUidPrivileged(int uid) { + return mComputer.isUidPrivileged(uid); + } + + /** + * Logs process start information (including base APK hash) to the security log. + * @hide + */ + @Override + public void logAppProcessStartIfNeeded(String packageName, String processName, int uid, + String seinfo, String apkFile, int pid) { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return; + } + if (!SecurityLog.isLoggingEnabled()) { + return; + } + mProcessLoggingHandler.logAppProcessStart(mContext, mPmInternal, apkFile, packageName, + processName, uid, seinfo, pid); + } + + @Override + public int movePackage(final String packageName, final String volumeUuid) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.MOVE_PACKAGE, null); + + final int callingUid = Binder.getCallingUid(); + final UserHandle user = new UserHandle(UserHandle.getUserId(callingUid)); + final int moveId = mNextMoveId.getAndIncrement(); + mHandler.post(() -> { + try { + MovePackageHelper movePackageHelper = + new MovePackageHelper(PackageManagerService.this); + movePackageHelper.movePackageInternal( + packageName, volumeUuid, moveId, callingUid, user); + } catch (PackageManagerException e) { + Slog.w(PackageManagerService.TAG, "Failed to move " + packageName, e); + mMoveCallbacks.notifyStatusChanged(moveId, e.error); + } + }); + return moveId; + } + + @Override + public int movePrimaryStorage(String volumeUuid) throws RemoteException { + mContext.enforceCallingOrSelfPermission(Manifest.permission.MOVE_PACKAGE, null); + + final int realMoveId = mNextMoveId.getAndIncrement(); + final Bundle extras = new Bundle(); + extras.putString(VolumeRecord.EXTRA_FS_UUID, volumeUuid); + mMoveCallbacks.notifyCreated(realMoveId, extras); + + final IPackageMoveObserver callback = new IPackageMoveObserver.Stub() { + @Override + public void onCreated(int moveId, Bundle extras) { + // Ignored + } + + @Override + public void onStatusChanged(int moveId, int status, long estMillis) { + mMoveCallbacks.notifyStatusChanged(realMoveId, status, estMillis); + } + }; + + final StorageManager storage = mInjector.getSystemService(StorageManager.class); + storage.setPrimaryStorageUuid(volumeUuid, callback); + return realMoveId; + } + + @Override + public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap, + String loaderIsa) { + int callingUid = Binder.getCallingUid(); + if (PackageManagerService.PLATFORM_PACKAGE_NAME.equals(loadingPackageName) && callingUid != Process.SYSTEM_UID) { + Slog.w(PackageManagerService.TAG, "Non System Server process reporting dex loads as system server. uid=" + + callingUid); + // Do not record dex loads from processes pretending to be system server. + // Only the system server should be assigned the package "android", so reject calls + // that don't satisfy the constraint. + // + // notifyDexLoad is a PM API callable from the app process. So in theory, apps could + // craft calls to this API and pretend to be system server. Doing so poses no particular + // danger for dex load reporting or later dexopt, however it is a sensible check to do + // in order to verify the expectations. + return; + } + + int userId = UserHandle.getCallingUserId(); + ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); + if (ai == null) { + Slog.w(PackageManagerService.TAG, "Loading a package that does not exist for the calling user. package=" + + loadingPackageName + ", user=" + userId); + return; + } + mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId, + Process.isIsolated(callingUid)); + } + + @Override + public void notifyPackageUse(String packageName, int reason) { + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + final boolean notify; + if (getInstantAppPackageName(callingUid) != null) { + notify = isCallerSameApp(packageName, callingUid); + } else { + notify = !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID); + } + if (!notify) { + return; + } + + notifyPackageUseInternal(packageName, reason); + } + + @Override + public void overrideLabelAndIcon(@NonNull ComponentName componentName, + @NonNull String nonLocalizedLabel, int icon, int userId) { + if (TextUtils.isEmpty(nonLocalizedLabel)) { + throw new IllegalArgumentException("Override label should be a valid String"); + } + updateComponentLabelIcon(componentName, nonLocalizedLabel, icon, userId); + } + + /** + * Ask the package manager to perform a dex-opt with the given compiler filter. + * + * Note: exposed only for the shell command to allow moving packages explicitly to a + * definite state. + */ + @Override + public boolean performDexOptMode(String packageName, + boolean checkProfiles, String targetCompilerFilter, boolean force, + boolean bootComplete, String splitName) { + return mDexOptHelper.performDexOptMode(packageName, checkProfiles, targetCompilerFilter, + force, bootComplete, splitName); + } + + /** + * Ask the package manager to perform a dex-opt with the given compiler filter on the + * secondary dex files belonging to the given package. + * + * Note: exposed only for the shell command to allow moving packages explicitly to a + * definite state. + */ + @Override + public boolean performDexOptSecondary(String packageName, String compilerFilter, + boolean force) { + return mDexOptHelper.performDexOptSecondary(packageName, compilerFilter, force); + } + + @NonNull + @Override + public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, + int uid, @PackageManager.ComponentInfoFlagsBits long flags, + @Nullable String metaDataKey) { + return mComputer.queryContentProviders(processName, uid, flags, metaDataKey); + } + + @NonNull + @Override + public ParceledListSlice<InstrumentationInfo> queryInstrumentation( + @NonNull String targetPackage, int flags) { + return mComputer.queryInstrumentation(targetPackage, flags); + } + + @Override + public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivities(Intent intent, + String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities"); + + return new ParceledListSlice<>(snapshotComputer().queryIntentActivitiesInternal(intent, + resolvedType, flags, userId)); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + + @Override + public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivityOptions(ComponentName caller, + Intent[] specifics, String[] specificTypes, Intent intent, + String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + return new ParceledListSlice<>(mResolveIntentHelper.queryIntentActivityOptionsInternal( + snapshotComputer(), caller, specifics, specificTypes, intent, resolvedType, flags, + userId)); + } + + @Override + public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent, + String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + return new ParceledListSlice<>(mResolveIntentHelper.queryIntentContentProvidersInternal( + snapshotComputer(), intent, resolvedType, flags, userId)); + } + + @Override + public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent, + String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal( + snapshotComputer(), intent, resolvedType, flags, userId, Binder.getCallingUid())); + } + + @Override + public @NonNull ParceledListSlice<ResolveInfo> queryIntentServices(Intent intent, + String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + final int callingUid = Binder.getCallingUid(); + return new ParceledListSlice<>(snapshotComputer().queryIntentServicesInternal( + intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/)); + } + + @Override + public ParceledListSlice<PackageManager.Property> queryProperty( + String propertyName, @PackageManager.PropertyLocation int componentType) { + Objects.requireNonNull(propertyName); + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getCallingUserId(); + final List<PackageManager.Property> result = + mPackageProperty.queryProperty(propertyName, componentType, packageName -> { + final PackageStateInternal ps = getPackageStateInternal(packageName); + return shouldFilterApplication(ps, callingUid, callingUserId); + }); + if (result == null) { + return ParceledListSlice.emptyList(); + } + return new ParceledListSlice<>(result); + } + + @Deprecated + public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) { + mComputer.querySyncProviders(mSafeMode, outNames, outInfo); + } + + /** + * Reconcile the information we have about the secondary dex files belonging to + * {@code packageName} and the actual dex files. For all dex files that were + * deleted, update the internal records and delete the generated oat files. + */ + @Override + public void reconcileSecondaryDexFiles(String packageName) { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + return; + } else if (isInstantAppInternal( + packageName, UserHandle.getCallingUserId(), Process.SYSTEM_UID)) { + return; + } + mDexManager.reconcileSecondaryDexFiles(packageName); + } + + @Override + public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule, + IDexModuleRegisterCallback callback) { + int userId = UserHandle.getCallingUserId(); + ApplicationInfo ai = getApplicationInfo(packageName, /*flags*/ 0, userId); + DexManager.RegisterDexModuleResult result; + if (ai == null) { + Slog.w(PackageManagerService.TAG, "Registering a dex module for a package that does not exist for the" + + " calling user. package=" + packageName + ", user=" + userId); + result = new DexManager.RegisterDexModuleResult(false, "Package not installed"); + } else { + result = mDexManager.registerDexModule(ai, dexModulePath, isSharedModule, userId); + } + + if (callback != null) { + mHandler.post(() -> { + try { + callback.onDexModuleRegistered(dexModulePath, result.success, result.message); + } catch (RemoteException e) { + Slog.w(PackageManagerService.TAG, "Failed to callback after module registration " + dexModulePath, e); + } + }); + } + } + + @Override + public void registerMoveCallback(IPackageMoveObserver callback) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + mMoveCallbacks.register(callback); + } + + // NOTE: Can't remove due to unsupported app usage + @Override + public void removePermission(String permName) { + // Because this is accessed via the package manager service AIDL, + // go through the permission manager service AIDL + mContext.getSystemService(PermissionManager.class).removePermission(permName); + } + + @Override + public void replacePreferredActivity(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, int userId) { + mPreferredActivityHelper.replacePreferredActivity(new WatchedIntentFilter(filter), + match, set, activity, userId); + } + + @Override + public void resetApplicationPreferences(int userId) { + mPreferredActivityHelper.resetApplicationPreferences(userId); + } + + @Override + public ProviderInfo resolveContentProvider(String name, + @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + return mComputer.resolveContentProvider(name, flags, userId, Binder.getCallingUid()); + } + + @Override + public ResolveInfo resolveIntent(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + return mResolveIntentHelper.resolveIntentInternal(snapshotComputer(), intent, resolvedType, + flags, 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid()); + } + + @Override + public ResolveInfo resolveService(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlagsBits long flags, int userId) { + final int callingUid = Binder.getCallingUid(); + return mResolveIntentHelper.resolveServiceInternal(snapshotComputer(), intent, resolvedType, + flags, userId, callingUid); + } + + @Override + public void restoreDefaultApps(byte[] backup, int userId) { + mPreferredActivityHelper.restoreDefaultApps(backup, userId); + } + + @Override + public void restoreDomainVerification(byte[] backup, int userId) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system may call restorePreferredActivities()"); + } + + try { + ByteArrayInputStream input = new ByteArrayInputStream(backup); + TypedXmlPullParser parser = Xml.resolvePullParser(input); + + // User ID input isn't necessary here as it assumes the user integers match and that + // the only states inside the backup XML are for the target user. + mDomainVerificationManager.restoreSettings(snapshotComputer(), parser); + input.close(); + } catch (Exception e) { + if (PackageManagerService.DEBUG_BACKUP) { + Slog.e(PackageManagerService.TAG, "Exception restoring domain verification: " + e.getMessage()); + } + } + } + + @Override + public void restoreLabelAndIcon(@NonNull ComponentName componentName, int userId) { + updateComponentLabelIcon(componentName, null, null, userId); + } + + @Override + public void restorePreferredActivities(byte[] backup, int userId) { + mPreferredActivityHelper.restorePreferredActivities(backup, userId); + } + + @Override + public void sendDeviceCustomizationReadyBroadcast() { + mContext.enforceCallingPermission(Manifest.permission.SEND_DEVICE_CUSTOMIZATION_READY, + "sendDeviceCustomizationReadyBroadcast"); + + final long ident = Binder.clearCallingIdentity(); + try { + BroadcastHelper.sendDeviceCustomizationReadyBroadcast(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void setApplicationCategoryHint(String packageName, int categoryHint, + String callerPackageName) { + if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + throw new SecurityException("Instant applications don't have access to this method"); + } + mInjector.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(), + callerPackageName); + + final PackageStateMutator.InitialState initialState = recordInitialState(); + + final FunctionalUtils.ThrowingFunction<Computer, PackageStateMutator.Result> + implementation = computer -> { + PackageStateInternal packageState = computer.getPackageStateFiltered(packageName, + Binder.getCallingUid(), UserHandle.getCallingUserId()); + if (packageState == null) { + throw new IllegalArgumentException("Unknown target package " + packageName); + } + + if (!Objects.equals(callerPackageName, + packageState.getInstallSource().installerPackageName)) { + throw new IllegalArgumentException("Calling package " + callerPackageName + + " is not installer for " + packageName); + } + + if (packageState.getCategoryOverride() != categoryHint) { + return commitPackageStateMutation(initialState, + packageName, state -> state.setCategoryOverride(categoryHint)); + } else { + return null; + } + }; + + PackageStateMutator.Result result = implementation.apply(snapshotComputer()); + if (result != null && result.isStateChanged() && !result.isSpecificPackageNull()) { + // TODO: Specific return value of what state changed? + // The installer on record might have changed, retry with lock + synchronized (mPackageStateWriteLock) { + result = implementation.apply(snapshotComputer()); + } + } + + if (result != null && result.isCommitted()) { + scheduleWriteSettings(); + } + } + + @Override + public void setApplicationEnabledSetting(String appPackageName, + int newState, int flags, int userId, String callingPackage) { + if (!mUserManager.exists(userId)) return; + if (callingPackage == null) { + callingPackage = Integer.toString(Binder.getCallingUid()); + } + + setEnabledSettings(List.of(new PackageManager.ComponentEnabledSetting(appPackageName, newState, flags)), + userId, callingPackage); + } + + @Override + public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, + int userId) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); + final int callingUid = Binder.getCallingUid(); + enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, + true /* checkShell */, "setApplicationHiddenSetting for user " + userId); + + if (hidden && isPackageDeviceAdmin(packageName, userId)) { + Slog.w(TAG, "Not hiding package " + packageName + ": has active device admin"); + return false; + } + + // Do not allow "android" is being disabled + if ("android".equals(packageName)) { + Slog.w(TAG, "Cannot hide package: android"); + return false; + } + + final long callingId = Binder.clearCallingIdentity(); + try { + final PackageStateInternal packageState = + mComputer.getPackageStateFiltered(packageName, callingUid, userId); + if (packageState == null) { + return false; + } + + // Cannot hide static shared libs as they are considered + // a part of the using app (emulating static linking). Also + // static libs are installed always on internal storage. + AndroidPackage pkg = packageState.getPkg(); + if (pkg != null) { + // Cannot hide SDK libs as they are controlled by SDK manager. + if (pkg.getSdkLibName() != null) { + Slog.w(TAG, "Cannot hide package: " + packageName + + " providing SDK library: " + + pkg.getSdkLibName()); + return false; + } + // Cannot hide static shared libs as they are considered + // a part of the using app (emulating static linking). Also + // static libs are installed always on internal storage. + if (pkg.getStaticSharedLibName() != null) { + Slog.w(TAG, "Cannot hide package: " + packageName + + " providing static shared library: " + + pkg.getStaticSharedLibName()); + return false; + } + } + // Only allow protected packages to hide themselves. + if (hidden && !UserHandle.isSameApp(callingUid, packageState.getAppId()) + && mProtectedPackages.isPackageStateProtected(userId, packageName)) { + Slog.w(TAG, "Not hiding protected package: " + packageName); + return false; + } + + if (packageState.getUserStateOrDefault(userId).isHidden() == hidden) { + return false; + } + + commitPackageStateMutation(null, packageName, packageState1 -> + packageState1.userState(userId).setHidden(hidden)); + + final PackageStateInternal newPackageState = getPackageStateInternal(packageName); + + if (hidden) { + killApplication(packageName, newPackageState.getAppId(), userId, "hiding pkg"); + sendApplicationHiddenForUser(packageName, newPackageState, userId); + } else { + sendPackageAddedForUser(packageName, newPackageState, userId, DataLoaderType.NONE); + } + + scheduleWritePackageRestrictions(userId); + return true; + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + @Override + public boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, + int userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.DELETE_PACKAGES, null); + PackageStateInternal packageState = getPackageStateInternal(packageName); + if (packageState != null && packageState.getPkg() != null) { + AndroidPackage pkg = packageState.getPkg(); + // Cannot block uninstall SDK libs as they are controlled by SDK manager. + if (pkg.getSdkLibName() != null) { + Slog.w(PackageManagerService.TAG, "Cannot block uninstall of package: " + packageName + + " providing SDK library: " + pkg.getSdkLibName()); + return false; + } + // Cannot block uninstall of static shared libs as they are + // considered a part of the using app (emulating static linking). + // Also static libs are installed always on internal storage. + if (pkg.getStaticSharedLibName() != null) { + Slog.w(PackageManagerService.TAG, "Cannot block uninstall of package: " + packageName + + " providing static shared library: " + pkg.getStaticSharedLibName()); + return false; + } + } + synchronized (mLock) { + mSettings.setBlockUninstallLPw(userId, packageName, blockUninstall); + } + + scheduleWritePackageRestrictions(userId); + return true; + } + + @Override + public void setComponentEnabledSetting(ComponentName componentName, + int newState, int flags, int userId) { + if (!mUserManager.exists(userId)) return; + + setEnabledSettings(List.of(new PackageManager.ComponentEnabledSetting(componentName, newState, flags)), + userId, null /* callingPackage */); + } + + @Override + public void setComponentEnabledSettings(List<PackageManager.ComponentEnabledSetting> settings, int userId) { + if (!mUserManager.exists(userId)) return; + if (settings == null || settings.isEmpty()) { + throw new IllegalArgumentException("The list of enabled settings is empty"); + } + + setEnabledSettings(settings, userId, null /* callingPackage */); + } + + @Override + public String[] setDistractingPackageRestrictionsAsUser(String[] packageNames, + int restrictionFlags, int userId) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, + "setDistractingPackageRestrictionsAsUser"); + + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID + && UserHandle.getUserId(callingUid) != userId) { + throw new SecurityException("Calling uid " + callingUid + " cannot call for user " + + userId); + } + Objects.requireNonNull(packageNames, "packageNames cannot be null"); + if (restrictionFlags != 0 + && !mSuspendPackageHelper.isSuspendAllowedForUser(userId, callingUid)) { + Slog.w(PackageManagerService.TAG, "Cannot restrict packages due to restrictions on user " + userId); + return packageNames; + } + + final List<String> changedPackagesList = new ArrayList<>(packageNames.length); + final IntArray changedUids = new IntArray(packageNames.length); + final List<String> unactionedPackages = new ArrayList<>(packageNames.length); + + ArraySet<String> changesToCommit = new ArraySet<>(); + Computer computer = snapshotComputer(); + final boolean[] canRestrict = (restrictionFlags != 0) + ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId, + callingUid) : null; + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + final PackageStateInternal packageState = + computer.getPackageStateInternal(packageName); + if (packageState == null + || computer.shouldFilterApplication(packageState, callingUid, userId)) { + Slog.w(PackageManagerService.TAG, "Could not find package setting for package: " + packageName + + ". Skipping..."); + unactionedPackages.add(packageName); + continue; + } + if (canRestrict != null && !canRestrict[i]) { + unactionedPackages.add(packageName); + continue; + } + final int oldDistractionFlags = packageState.getUserStateOrDefault(userId) + .getDistractionFlags(); + if (restrictionFlags != oldDistractionFlags) { + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + changesToCommit.add(packageName); + } + } + + commitPackageStateMutation(null, mutator -> { + final int size = changesToCommit.size(); + for (int index = 0; index < size; index++) { + mutator.forPackage(changesToCommit.valueAt(index)) + .userState(userId) + .setDistractionFlags(restrictionFlags); + } + }); + + if (!changedPackagesList.isEmpty()) { + final String[] changedPackages = changedPackagesList.toArray( + new String[changedPackagesList.size()]); + mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged( + changedPackages, changedUids.toArray(), userId, restrictionFlags)); + scheduleWritePackageRestrictions(userId); + } + return unactionedPackages.toArray(new String[0]); + } + + @Override + public void setHarmfulAppWarning(@NonNull String packageName, @Nullable CharSequence warning, + int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingAppId = UserHandle.getAppId(callingUid); + + enforceCrossUserPermission(callingUid, userId, true /*requireFullPermission*/, + true /*checkShell*/, "setHarmfulAppInfo"); + + if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID && + checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) { + throw new SecurityException("Caller must have the " + + SET_HARMFUL_APP_WARNINGS + " permission."); + } + + PackageStateMutator.Result result = commitPackageStateMutation(null, packageName, + packageState -> packageState.userState(userId) + .setHarmfulAppWarning(warning == null ? null : warning.toString())); + if (result.isSpecificPackageNull()) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + scheduleWritePackageRestrictions(userId); + } + + @Override + public void setHomeActivity(ComponentName comp, int userId) { + mPreferredActivityHelper.setHomeActivity(comp, userId); + } + + @Override + public boolean setInstallLocation(int loc) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS, + null); + if (getInstallLocation() == loc) { + return true; + } + if (loc == InstallLocationUtils.APP_INSTALL_AUTO + || loc == InstallLocationUtils.APP_INSTALL_INTERNAL + || loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) { + android.provider.Settings.Global.putInt(mContext.getContentResolver(), + android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc); + return true; + } + return false; + } + + @Override + public void setInstallerPackageName(String targetPackage, String installerPackageName) { + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + final FunctionalUtils.ThrowingCheckedFunction<Computer, Boolean, RuntimeException> + implementation = computer -> { + if (computer.getInstantAppPackageName(callingUid) != null) { + return false; + } + + PackageStateInternal targetPackageState = + computer.getPackageStateInternal(targetPackage); + if (targetPackageState == null + || computer.shouldFilterApplication(targetPackageState, callingUid, + callingUserId)) { + throw new IllegalArgumentException("Unknown target package: " + targetPackage); + } + + PackageStateInternal installerPackageState = null; + if (installerPackageName != null) { + installerPackageState = computer.getPackageStateInternal(installerPackageName); + if (installerPackageState == null + || shouldFilterApplication( + installerPackageState, callingUid, callingUserId)) { + throw new IllegalArgumentException("Unknown installer package: " + + installerPackageName); + } + } + + Signature[] callerSignature; + final int appId = UserHandle.getAppId(callingUid); + Pair<PackageStateInternal, SharedUserApi> either = + computer.getPackageOrSharedUser(appId); + if (either != null) { + if (either.first != null) { + callerSignature = either.first.getSigningDetails().getSignatures(); + } else { + callerSignature = either.second.getSigningDetails().getSignatures(); + } + } else { + throw new SecurityException("Unknown calling UID: " + callingUid); + } + + // Verify: can't set installerPackageName to a package that is + // not signed with the same cert as the caller. + if (installerPackageState != null) { + if (compareSignatures(callerSignature, + installerPackageState.getSigningDetails().getSignatures()) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as new installer package " + + installerPackageName); + } + } + + // Verify: if target already has an installer package, it must + // be signed with the same cert as the caller. + String targetInstallerPackageName = + targetPackageState.getInstallSource().installerPackageName; + PackageStateInternal targetInstallerPkgSetting = targetInstallerPackageName == null + ? null : computer.getPackageStateInternal(targetInstallerPackageName); + + if (targetInstallerPkgSetting != null) { + if (compareSignatures(callerSignature, + targetInstallerPkgSetting.getSigningDetails().getSignatures()) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as old installer package " + + targetInstallerPackageName); + } + } else if (mContext.checkCallingOrSelfPermission( + Manifest.permission.INSTALL_PACKAGES) != PERMISSION_GRANTED) { + // This is probably an attempt to exploit vulnerability b/150857253 of taking + // privileged installer permissions when the installer has been uninstalled or + // was never set. + EventLog.writeEvent(0x534e4554, "150857253", callingUid, ""); + + final long binderToken = Binder.clearCallingIdentity(); + try { + if (mInjector.getCompatibility().isChangeEnabledByUid( + PackageManagerService.THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE, + callingUid)) { + throw new SecurityException("Neither user " + callingUid + + " nor current process has " + + Manifest.permission.INSTALL_PACKAGES); + } else { + // If change disabled, fail silently for backwards compatibility + return false; + } + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + return true; + }; + PackageStateMutator.InitialState initialState = recordInitialState(); + boolean allowed = implementation.apply(snapshotComputer()); + if (allowed) { + // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames, + // should find an alternative which avoids any race conditions + PackageStateInternal targetPackageState; + synchronized (mLock) { + PackageStateMutator.Result result = commitPackageStateMutation(initialState, + targetPackage, state -> state.setInstaller(installerPackageName)); + if (result.isPackagesChanged() || result.isStateChanged()) { + synchronized (mPackageStateWriteLock) { + allowed = implementation.apply(snapshotComputer()); + if (allowed) { + commitPackageStateMutation(null, targetPackage, + state -> state.setInstaller(installerPackageName)); + } else { + return; + } + } + } + targetPackageState = getPackageStateInternal(targetPackage); + mSettings.addInstallerPackageNames(targetPackageState.getInstallSource()); + } + mAppsFilter.addPackage(targetPackageState); + scheduleWriteSettings(); + } + } + + @Override + public boolean setInstantAppCookie(String packageName, byte[] cookie, int userId) { + if (HIDE_EPHEMERAL_APIS) { + return true; + } + + enforceCrossUserPermission(Binder.getCallingUid(), userId, + true /* requireFullPermission */, true /* checkShell */, + "setInstantAppCookie"); + if (!isCallerSameApp(packageName, Binder.getCallingUid())) { + return false; + } + + PackageStateInternal packageState = getPackageStateInternal(packageName); + if (packageState == null || packageState.getPkg() == null) { + return false; + } + return mInstantAppRegistry.setInstantAppCookie(packageState.getPkg(), cookie, + mContext.getPackageManager().getInstantAppCookieMaxBytes(), userId); + } + + @Override + public void setKeepUninstalledPackages(List<String> packageList) { + mContext.enforceCallingPermission( + Manifest.permission.KEEP_UNINSTALLED_PACKAGES, + "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission"); + Objects.requireNonNull(packageList); + + setKeepUninstalledPackagesInternal(packageList); + } + + @Override + public void setLastChosenActivity(Intent intent, String resolvedType, int flags, + IntentFilter filter, int match, ComponentName activity) { + mPreferredActivityHelper.setLastChosenActivity(intent, resolvedType, flags, + new WatchedIntentFilter(filter), match, activity); + } + + @Override + public void setMimeGroup(String packageName, String mimeGroup, List<String> mimeTypes) { + enforceOwnerRights(packageName, Binder.getCallingUid()); + mimeTypes = CollectionUtils.emptyIfNull(mimeTypes); + final PackageStateInternal packageState = getPackageStateInternal(packageName); + Set<String> existingMimeTypes = packageState.getMimeGroups().get(mimeGroup); + if (existingMimeTypes == null) { + throw new IllegalArgumentException("Unknown MIME group " + mimeGroup + + " for package " + packageName); + } + if (existingMimeTypes.size() == mimeTypes.size() + && existingMimeTypes.containsAll(mimeTypes)) { + return; + } + + ArraySet<String> mimeTypesSet = new ArraySet<>(mimeTypes); + commitPackageStateMutation(null, packageName, packageStateWrite -> { + packageStateWrite.setMimeGroup(mimeGroup, mimeTypesSet); + }); + if (mComponentResolver.updateMimeGroup(snapshotComputer(), packageName, mimeGroup)) { + Binder.withCleanCallingIdentity(() -> + mPreferredActivityHelper.clearPackagePreferredActivities(packageName, + UserHandle.USER_ALL)); + } + + scheduleWriteSettings(); + } + + @Override + public void setPackageStoppedState(String packageName, boolean stopped, int userId) { + PackageManagerService.this + .setPackageStoppedState(snapshotComputer(), packageName, stopped, userId); + } + + @Override + public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, + PersistableBundle appExtras, PersistableBundle launcherExtras, + SuspendDialogInfo dialogInfo, String callingPackage, int userId) { + final int callingUid = Binder.getCallingUid(); + enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId, + "setPackagesSuspendedAsUser"); + return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames, + suspended, appExtras, launcherExtras, dialogInfo, callingPackage, userId, + callingUid); + } + + @Override + public boolean setRequiredForSystemUser(String packageName, boolean requiredForSystemUser) { + PackageManagerServiceUtils.enforceSystemOrRoot( + "setRequiredForSystemUser can only be run by the system or root"); + + PackageStateMutator.Result result = commitPackageStateMutation(null, packageName, + packageState -> packageState.setRequiredForSystemUser(requiredForSystemUser)); + if (!result.isCommitted()) { + return false; + } + + scheduleWriteSettings(); + return true; + } + + @Override + public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) { + Preconditions.checkArgumentNonnegative(version); + Preconditions.checkArgumentNonnegative(userId); + enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions( + "setRuntimePermissionVersion"); + mSettings.setDefaultRuntimePermissionsVersion(version, userId); + } + + @Override + public void setSplashScreenTheme(@NonNull String packageName, @Nullable String themeId, + int userId) { + final int callingUid = Binder.getCallingUid(); + enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "setSplashScreenTheme"); + enforceOwnerRights(packageName, callingUid); + + PackageStateInternal packageState = getPackageStateInstalledFiltered(packageName, + callingUid, userId); + if (packageState == null) { + return; + } + + commitPackageStateMutation(null, packageName, state -> + state.userState(userId).setSplashScreenTheme(themeId)); + } + + @Override + public void setSystemAppHiddenUntilInstalled(String packageName, boolean hidden) { + final int callingUid = Binder.getCallingUid(); + final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID + || callingUid == Process.SYSTEM_UID; + if (!calledFromSystemOrPhone) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, + "setSystemAppHiddenUntilInstalled"); + } + + final PackageStateInternal stateRead = getPackageStateInternal(packageName); + if (stateRead == null || !stateRead.isSystem() || stateRead.getPkg() == null) { + return; + } + if (stateRead.getPkg().isCoreApp() && !calledFromSystemOrPhone) { + throw new SecurityException("Only system or phone callers can modify core apps"); + } + + commitPackageStateMutation(null, mutator -> { + mutator.forPackage(packageName) + .setHiddenUntilInstalled(hidden); + mutator.forDisabledSystemPackage(packageName) + .setHiddenUntilInstalled(hidden); + }); + } + + @Override + public boolean setSystemAppInstallState(String packageName, boolean installed, int userId) { + final int callingUid = Binder.getCallingUid(); + final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID + || callingUid == Process.SYSTEM_UID; + if (!calledFromSystemOrPhone) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, + "setSystemAppHiddenUntilInstalled"); + } + + final PackageStateInternal packageState = getPackageStateInternal(packageName); + // The target app should always be in system + if (packageState == null || !packageState.isSystem() || packageState.getPkg() == null) { + return false; + } + if (packageState.getPkg().isCoreApp() && !calledFromSystemOrPhone) { + throw new SecurityException("Only system or phone callers can modify core apps"); + } + // Check if the install state is the same + if (packageState.getUserStateOrDefault(userId).isInstalled() == installed) { + return false; + } + + final long callingId = Binder.clearCallingIdentity(); + try { + if (installed) { + // install the app from uninstalled state + installExistingPackageAsUser( + packageName, + userId, + PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, + PackageManager.INSTALL_REASON_DEVICE_SETUP, + null); + return true; + } + + // uninstall the app from installed state + deletePackageVersioned( + new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), + new PackageManager.LegacyPackageDeleteObserver(null).getBinder(), + userId, + PackageManager.DELETE_SYSTEM_APP); + return true; + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, null); + commitPackageStateMutation(null, packageName, state -> + state.setUpdateAvailable(updateAvailable)); + } + + @Override + public void unregisterMoveCallback(IPackageMoveObserver callback) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + mMoveCallbacks.unregister(callback); + } + + @Deprecated + @Override + public boolean updateIntentVerificationStatus(String packageName, int status, int userId) { + return mDomainVerificationManager.setLegacyUserState(packageName, userId, status); + } + + @Deprecated + @Override + public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) { + DomainVerificationProxyV1.queueLegacyVerifyResult(mContext, mDomainVerificationConnection, + id, verificationCode, failedDomains, Binder.getCallingUid()); + } + + @Override + public void verifyPendingInstall(int id, int verificationCode) throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.PACKAGE_VERIFICATION_AGENT, + "Only package verification agents can verify applications"); + final int callingUid = Binder.getCallingUid(); + + final Message msg = mHandler.obtainMessage(PackageManagerService.PACKAGE_VERIFIED); + final PackageVerificationResponse response = new PackageVerificationResponse( + verificationCode, callingUid); + msg.arg1 = id; + msg.obj = response; + mHandler.sendMessage(msg); + } + + @Override + public void requestPackageChecksums(@NonNull String packageName, boolean includeSplits, + @Checksum.TypeMask int optional, @Checksum.TypeMask int required, + @Nullable List trustedInstallers, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId) { + requestChecksumsInternal(packageName, includeSplits, optional, required, trustedInstallers, + onChecksumsReadyListener, userId, mInjector.getBackgroundExecutor(), + mInjector.getBackgroundHandler()); + } + + @Override + public void notifyPackagesReplacedReceived(String[] packages) { + Computer computer = snapshotComputer(); + ArraySet<String> packagesToNotify = computer.getNotifyPackagesForReplacedReceived(packages); + for (int index = 0; index < packagesToNotify.size(); index++) { + notifyInstallObserver(packagesToNotify.valueAt(index), false /* killApp */); + } + } + + @Override + public boolean canPackageQuery(@NonNull String sourcePackageName, + @NonNull String targetPackageName, @UserIdInt int userId) { + return mComputer.canPackageQuery(sourcePackageName, targetPackageName, userId); + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException) + && !(e instanceof ParcelableException)) { + Slog.wtf(TAG, "Package Manager Unexpected Exception", e); + } + throw e; + } + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + (new PackageManagerShellCommand(mIPackageManager, + mContext,mDomainVerificationManager.getShell())) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + @SuppressWarnings("resource") + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; + new DumpHelper(PackageManagerService.this).doDump(fd, pw, args); + } + } + private class PackageManagerLocalImpl implements PackageManagerLocal { } @@ -6865,7 +7173,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean isInstantApp(String packageName, int userId) { - return PackageManagerService.this.isInstantApp(packageName, userId); + return PackageManagerService.this.mIPackageManager.isInstantApp(packageName, userId); } @Override @@ -7226,7 +7534,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public String getNameForUid(int uid) { - return PackageManagerService.this.getNameForUid(uid); + return mIPackageManager.getNameForUid(uid); } @Override @@ -7471,7 +7779,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean isOnlyCoreApps() { - return PackageManagerService.this.isOnlyCoreApps(); + return mIPackageManager.isOnlyCoreApps(); } @Override @@ -7481,6 +7789,11 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public void freeAllAppCacheAboveQuota(@NonNull String volumeUuid) throws IOException { + PackageManagerService.this.freeAllAppCacheAboveQuota(volumeUuid); + } + + @Override public void forEachPackageSetting(Consumer<PackageSetting> actionLocked) { PackageManagerService.this.forEachPackageSetting(actionLocked); } @@ -7558,7 +7871,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void finishPackageInstall(int token, boolean didLaunch) { - PackageManagerService.this.finishPackageInstall(token, didLaunch); + mIPackageManager.finishPackageInstall(token, didLaunch); } @Nullable @@ -7803,6 +8116,16 @@ public class PackageManagerService extends IPackageManager.Stub public Computer snapshot() { return snapshotComputer(); } + + @Override + public void shutdown() { + PackageManagerService.this.shutdown(); + } + + @Override + public DynamicCodeLogger getDynamicCodeLogger() { + return PackageManagerService.this.getDexManager().getDynamicCodeLogger(); + } } private boolean setEnabledOverlayPackages(@UserIdInt int userId, @@ -7886,23 +8209,6 @@ public class PackageManagerService extends IPackageManager.Stub return true; } - @Override - public int getRuntimePermissionsVersion(@UserIdInt int userId) { - Preconditions.checkArgumentNonnegative(userId); - enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions( - "getRuntimePermissionVersion"); - return mSettings.getDefaultRuntimePermissionsVersion(userId); - } - - @Override - public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) { - Preconditions.checkArgumentNonnegative(version); - Preconditions.checkArgumentNonnegative(userId); - enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions( - "setRuntimePermissionVersion"); - mSettings.setDefaultRuntimePermissionsVersion(version, userId); - } - private void enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions( @NonNull String message) { if (mContext.checkCallingOrSelfPermission( @@ -8031,23 +8337,6 @@ public class PackageManagerService extends IPackageManager.Stub return mPackageUsage.isHistoricalPackageUsageAvailable(); } - /** - * Logs process start information (including base APK hash) to the security log. - * @hide - */ - @Override - public void logAppProcessStartIfNeeded(String packageName, String processName, int uid, - String seinfo, String apkFile, int pid) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return; - } - if (!SecurityLog.isLoggingEnabled()) { - return; - } - mProcessLoggingHandler.logAppProcessStart(mContext, mPmInternal, apkFile, packageName, - processName, uid, seinfo, pid); - } - public CompilerStats.PackageStats getOrCreateCompilerPackageStats(AndroidPackage pkg) { return getOrCreateCompilerPackageStats(pkg.getPackageName()); } @@ -8056,26 +8345,6 @@ public class PackageManagerService extends IPackageManager.Stub return mCompilerStats.getOrCreatePackageStats(pkgName); } - @Override - public boolean isAutoRevokeWhitelisted(String packageName) { - int mode = mInjector.getSystemService(AppOpsManager.class).checkOpNoThrow( - AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, - Binder.getCallingUid(), packageName); - return mode == MODE_IGNORED; - } - - @PackageManager.InstallReason - @Override - public int getInstallReason(@NonNull String packageName, @UserIdInt int userId) { - return mComputer.getInstallReason(packageName, userId); - } - - @Override - public boolean canRequestPackageInstalls(String packageName, int userId) { - return mComputer.canRequestPackageInstalls(packageName, Binder.getCallingUid(), userId, - true /* throwIfPermNotDeclared*/); - } - /** * Returns true if the system or user is explicitly preventing an otherwise valid installer to * complete an install. This includes checks like unknown sources and user restrictions. @@ -8084,45 +8353,35 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.isInstallDisabledForPackage(packageName, uid, userId); } - @Override - public ComponentName getInstantAppResolverSettingsComponent() { - return mInstantAppResolverSettingsComponent; - } - - @Override - public ComponentName getInstantAppInstallerComponent() { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return null; + private void grantImplicitAccess(@NonNull Computer snapshot, @UserIdInt int userId, + Intent intent, @AppIdInt int recipientAppId, int visibleUid, boolean direct, + boolean retainOnUpdate) { + final AndroidPackage visiblePackage = snapshot.getPackage(visibleUid); + final int recipientUid = UserHandle.getUid(userId, recipientAppId); + if (visiblePackage == null || snapshot.getPackage(recipientUid) == null) { + return; } - return mInstantAppInstallerActivity == null - ? null : mInstantAppInstallerActivity.getComponentName(); - } - @Override - public String getInstantAppAndroidId(String packageName, int userId) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS, - "getInstantAppAndroidId"); - enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, - false /* checkShell */, "getInstantAppAndroidId"); - // Make sure the target is an Instant App. - if (!isInstantApp(packageName, userId)) { - return null; + final boolean instantApp = snapshot.isInstantAppInternal( + visiblePackage.getPackageName(), userId, visibleUid); + final boolean accessGranted; + if (instantApp) { + if (!direct) { + // if the interaction that lead to this granting access to an instant app + // was indirect (i.e.: URI permission grant), do not actually execute the + // grant. + return; + } + accessGranted = mInstantAppRegistry.grantInstantAccess(userId, intent, + recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/); + } else { + accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid, + retainOnUpdate); } - return mInstantAppRegistry.getInstantAppAndroidId(packageName, userId); - } - @Override - public void grantImplicitAccess(int recipientUid, @NonNull String visibleAuthority) { - final int callingUid = Binder.getCallingUid(); - final int recipientUserId = UserHandle.getUserId(recipientUid); - final ProviderInfo providerInfo = - mComputer.getGrantImplicitAccessProviderInfo(recipientUid, visibleAuthority); - if (providerInfo == null) { - return; + if (accessGranted) { + ApplicationPackageManager.invalidateGetPackagesForUidCache(); } - int visibleUid = providerInfo.applicationInfo.uid; - mPmInternal.grantImplicitAccess(recipientUserId, null /*Intent*/, - UserHandle.getAppId(recipientUid), visibleUid, false /*direct*/); } boolean canHaveOatDir(String packageName) { @@ -8148,100 +8407,6 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.getUnusedPackages(downgradeTimeThresholdMillis); } - @Override - public void setHarmfulAppWarning(@NonNull String packageName, @Nullable CharSequence warning, - int userId) { - final int callingUid = Binder.getCallingUid(); - final int callingAppId = UserHandle.getAppId(callingUid); - - enforceCrossUserPermission(callingUid, userId, true /*requireFullPermission*/, - true /*checkShell*/, "setHarmfulAppInfo"); - - if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID && - checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) { - throw new SecurityException("Caller must have the " - + SET_HARMFUL_APP_WARNINGS + " permission."); - } - - PackageStateMutator.Result result = commitPackageStateMutation(null, packageName, - packageState -> packageState.userState(userId) - .setHarmfulAppWarning(warning == null ? null : warning.toString())); - if (result.isSpecificPackageNull()) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - scheduleWritePackageRestrictions(userId); - } - - @Nullable - @Override - public CharSequence getHarmfulAppWarning(@NonNull String packageName, @UserIdInt int userId) { - return mComputer.getHarmfulAppWarning(packageName, userId); - } - - @Override - public boolean isPackageStateProtected(@NonNull String packageName, @UserIdInt int userId) { - final int callingUid = Binder.getCallingUid(); - final int callingAppId = UserHandle.getAppId(callingUid); - - enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, - true /*checkShell*/, "isPackageStateProtected"); - - if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID - && checkUidPermission(MANAGE_DEVICE_ADMINS, callingUid) != PERMISSION_GRANTED) { - throw new SecurityException("Caller must have the " - + MANAGE_DEVICE_ADMINS + " permission."); - } - - return mProtectedPackages.isPackageStateProtected(userId, packageName); - } - - @Override - public void sendDeviceCustomizationReadyBroadcast() { - mContext.enforceCallingPermission(Manifest.permission.SEND_DEVICE_CUSTOMIZATION_READY, - "sendDeviceCustomizationReadyBroadcast"); - - final long ident = Binder.clearCallingIdentity(); - try { - BroadcastHelper.sendDeviceCustomizationReadyBroadcast(); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @Override - public void setMimeGroup(String packageName, String mimeGroup, List<String> mimeTypes) { - enforceOwnerRights(packageName, Binder.getCallingUid()); - mimeTypes = CollectionUtils.emptyIfNull(mimeTypes); - final PackageStateInternal packageState = getPackageStateInternal(packageName); - Set<String> existingMimeTypes = packageState.getMimeGroups().get(mimeGroup); - if (existingMimeTypes == null) { - throw new IllegalArgumentException("Unknown MIME group " + mimeGroup - + " for package " + packageName); - } - if (existingMimeTypes.size() == mimeTypes.size() - && existingMimeTypes.containsAll(mimeTypes)) { - return; - } - - ArraySet<String> mimeTypesSet = new ArraySet<>(mimeTypes); - commitPackageStateMutation(null, packageName, packageStateWrite -> { - packageStateWrite.setMimeGroup(mimeGroup, mimeTypesSet); - }); - if (mComponentResolver.updateMimeGroup(snapshotComputer(), packageName, mimeGroup)) { - Binder.withCleanCallingIdentity(() -> - mPreferredActivityHelper.clearPackagePreferredActivities(packageName, - UserHandle.USER_ALL)); - } - - scheduleWriteSettings(); - } - - @Override - public List<String> getMimeGroup(String packageName, String mimeGroup) { - enforceOwnerRights(packageName, Binder.getCallingUid()); - return getMimeGroupInternal(packageName, mimeGroup); - } - private List<String> getMimeGroupInternal(String packageName, String mimeGroup) { final PackageStateInternal packageState = getPackageStateInternal(packageName); if (packageState == null) { @@ -8257,32 +8422,6 @@ public class PackageManagerService extends IPackageManager.Stub return new ArrayList<>(mimeTypes); } - @Override - public void setSplashScreenTheme(@NonNull String packageName, @Nullable String themeId, - int userId) { - final int callingUid = Binder.getCallingUid(); - enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, - false /* checkShell */, "setSplashScreenTheme"); - enforceOwnerRights(packageName, callingUid); - - PackageStateInternal packageState = getPackageStateInstalledFiltered(packageName, - callingUid, userId); - if (packageState == null) { - return; - } - - commitPackageStateMutation(null, packageName, state -> - state.userState(userId).setSplashScreenTheme(themeId)); - } - - @Override - public String getSplashScreenTheme(@NonNull String packageName, int userId) { - PackageStateInternal packageState = - getPackageStateInstalledFiltered(packageName, Binder.getCallingUid(), userId); - return packageState == null ? null - : packageState.getUserStateOrDefault(userId).getSplashScreenTheme(); - } - /** * Temporary method that wraps mSettings.writeLPr() and calls mPermissionManager's * writeLegacyPermissionsTEMP() beforehand. @@ -8296,21 +8435,6 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public IBinder getHoldLockToken() { - if (!Build.IS_DEBUGGABLE) { - throw new SecurityException("getHoldLockToken requires a debuggable build"); - } - - mContext.enforceCallingPermission( - Manifest.permission.INJECT_EVENTS, - "getHoldLockToken requires INJECT_EVENTS permission"); - - final Binder token = new Binder(); - token.attachInterface(this, "holdLock:" + Binder.getCallingUid()); - return token; - } - - @Override public void verifyHoldLockToken(IBinder token) { if (!Build.IS_DEBUGGABLE) { throw new SecurityException("holdLock requires a debuggable build"); @@ -8325,15 +8449,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public void holdLock(IBinder token, int durationMs) { - mTestUtilityService.verifyHoldLockToken(token); - - synchronized (mLock) { - SystemClock.sleep(durationMs); - } - } - static String getDefaultTimeouts() { final long token = Binder.clearCallingIdentity(); try { @@ -8431,16 +8546,6 @@ public class PackageManagerService extends IPackageManager.Stub return result.toArray(new PerUidReadTimeouts[result.size()]); } - @Override - public void setKeepUninstalledPackages(List<String> packageList) { - mContext.enforceCallingPermission( - Manifest.permission.KEEP_UNINSTALLED_PACKAGES, - "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission"); - Objects.requireNonNull(packageList); - - setKeepUninstalledPackagesInternal(packageList); - } - private void setKeepUninstalledPackagesInternal(List<String> packageList) { Preconditions.checkNotNull(packageList); synchronized (mKeepUninstalledPackages) { @@ -8462,19 +8567,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @Override - public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage, - String featureId, int userId) throws RemoteException { - return mResolveIntentHelper.getLaunchIntentSenderForPackage(snapshotComputer(), - packageName, callingPackage, featureId, userId); - } - - @Override - public boolean canPackageQuery(@NonNull String sourcePackageName, - @NonNull String targetPackageName, @UserIdInt int userId) { - return mComputer.canPackageQuery(sourcePackageName, targetPackageName, userId); - } - boolean getSafeMode() { return mSafeMode; } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 8d3fbf7fc679..2a1a99068d45 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -850,6 +850,8 @@ public class PackageManagerServiceUtils { ret.recommendedInstallLocation = recommendedInstallLocation; ret.multiArch = pkg.isMultiArch(); ret.debuggable = pkg.isDebuggable(); + ret.isSdkLibrary = pkg.isIsSdkLibrary(); + return ret; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 500b4ec70c44..62e9d372f09d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -131,6 +131,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -144,6 +145,10 @@ class PackageManagerShellCommand extends ShellCommand { private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/"; private static final int DEFAULT_STAGED_READY_TIMEOUT_MS = 60 * 1000; private static final String TAG = "PackageManagerShellCommand"; + private static final Set<String> UNSUPPORTED_INSTALL_CMD_OPTS = Set.of( + "--multi-package" + ); + private static final Set<String> UNSUPPORTED_SESSION_CREATE_OPTS = Collections.emptySet(); final IPackageManager mInterface; final LegacyPermissionManagerInternal mLegacyPermissionManager; @@ -159,9 +164,9 @@ class PackageManagerShellCommand extends ShellCommand { private static final SecureRandom RANDOM = new SecureRandom(); - PackageManagerShellCommand(@NonNull PackageManagerService service, + PackageManagerShellCommand(@NonNull IPackageManager packageManager, @NonNull Context context, @NonNull DomainVerificationShell domainVerificationShell) { - mInterface = service; + mInterface = packageManager; mLegacyPermissionManager = LocalServices.getService(LegacyPermissionManagerInternal.class); mPermissionManager = context.getSystemService(PermissionManager.class); mContext = context; @@ -1330,7 +1335,7 @@ class PackageManagerShellCommand extends ShellCommand { } private int runStreamingInstall() throws RemoteException { - final InstallParams params = makeInstallParams(); + final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS); if (params.sessionParams.dataLoaderParams == null) { params.sessionParams.setDataLoaderParams( PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this)); @@ -1339,7 +1344,7 @@ class PackageManagerShellCommand extends ShellCommand { } private int runIncrementalInstall() throws RemoteException { - final InstallParams params = makeInstallParams(); + final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS); if (params.sessionParams.dataLoaderParams == null) { params.sessionParams.setDataLoaderParams( PackageManagerShellCommandDataLoader.getIncrementalDataLoaderParams(this)); @@ -1348,7 +1353,7 @@ class PackageManagerShellCommand extends ShellCommand { } private int runInstall() throws RemoteException { - return doRunInstall(makeInstallParams()); + return doRunInstall(makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS)); } private int doRunInstall(final InstallParams params) throws RemoteException { @@ -1500,7 +1505,7 @@ class PackageManagerShellCommand extends ShellCommand { private int runInstallCreate() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); - final InstallParams installParams = makeInstallParams(); + final InstallParams installParams = makeInstallParams(UNSUPPORTED_SESSION_CREATE_OPTS); final int sessionId = doCreateSession(installParams.sessionParams, installParams.installerPackageName, installParams.userId); @@ -2896,7 +2901,7 @@ class PackageManagerShellCommand extends ShellCommand { long stagedReadyTimeoutMs = DEFAULT_STAGED_READY_TIMEOUT_MS; } - private InstallParams makeInstallParams() { + private InstallParams makeInstallParams(Set<String> unsupportedOptions) { final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL); final InstallParams params = new InstallParams(); @@ -2910,6 +2915,9 @@ class PackageManagerShellCommand extends ShellCommand { boolean replaceExisting = true; boolean forceNonStaged = false; while ((opt = getNextOption()) != null) { + if (unsupportedOptions.contains(opt)) { + throw new IllegalArgumentException("Unsupported option " + opt); + } switch (opt) { case "-r": // ignore break; @@ -3817,7 +3825,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--user USER_ID] INTENT"); pw.println(" Prints all broadcast receivers that can handle the given INTENT."); pw.println(""); - pw.println(" install [-rtfdgw] [-i PACKAGE] [--user USER_ID|all|current]"); + pw.println(" install [-rtfdg] [-i PACKAGE] [--user USER_ID|all|current]"); pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]"); pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]"); diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java index fdad83347f24..28ad4b61d8c7 100644 --- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java +++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java @@ -38,8 +38,6 @@ final class PackageRemovedInfo { String mInstallerPackageName; int mUid = -1; int mRemovedAppId = -1; - // If not -1, the app is going through an appId change - int mNewAppId = -1; int[] mOrigUsers; int[] mRemovedUsers = null; int[] mBroadcastUsers = null; @@ -67,22 +65,16 @@ final class PackageRemovedInfo { sendPackageRemovedBroadcastInternal(killApp, removedBySystem); } - void sendSystemPackageUpdatedBroadcasts(int newAppId) { + void sendSystemPackageUpdatedBroadcasts() { if (mIsRemovedPackageSystemUpdate) { - sendSystemPackageUpdatedBroadcastsInternal(newAppId); + sendSystemPackageUpdatedBroadcastsInternal(); } } - private void sendSystemPackageUpdatedBroadcastsInternal(int newAppId) { + private void sendSystemPackageUpdatedBroadcastsInternal() { Bundle extras = new Bundle(2); - extras.putInt(Intent.EXTRA_UID, newAppId); - // When appId changes, do not set the replacing extra - if (mNewAppId >= 0) { - extras.putBoolean(Intent.EXTRA_UID_CHANGING, true); - extras.putInt(Intent.EXTRA_PREVIOUS_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid); - } else { - extras.putBoolean(Intent.EXTRA_REPLACING, true); - } + extras.putInt(Intent.EXTRA_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid); + extras.putBoolean(Intent.EXTRA_REPLACING, true); mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, mRemovedPackage, extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null); if (mInstallerPackageName != null) { @@ -90,17 +82,13 @@ final class PackageRemovedInfo { mRemovedPackage, extras, 0 /*flags*/, mInstallerPackageName, null, null, null, null /* broadcastAllowList */, null); + mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, + mRemovedPackage, extras, 0 /*flags*/, + mInstallerPackageName, null, null, null, null /* broadcastAllowList */, + null); } - if (mNewAppId < 0) { - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage, - extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null); - if (mInstallerPackageName != null) { - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - mRemovedPackage, extras, 0 /*flags*/, - mInstallerPackageName, null, null, null, null /* broadcastAllowList */, - null); - } - } + mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage, + extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null); mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0, mRemovedPackage, null, null, null, null /* broadcastAllowList */, getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle()); @@ -134,15 +122,10 @@ final class PackageRemovedInfo { extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp); extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem); - - // When appId changes, do not set the replacing extra - if (mNewAppId >= 0) { - extras.putBoolean(Intent.EXTRA_UID_CHANGING, true); - extras.putInt(Intent.EXTRA_NEW_UID, mNewAppId); - } else if (mIsUpdate || mIsRemovedPackageSystemUpdate) { + final boolean isReplace = mIsUpdate || mIsRemovedPackageSystemUpdate; + if (isReplace) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } - extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers); if (mRemovedPackage != null) { mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, @@ -165,9 +148,9 @@ final class PackageRemovedInfo { } } if (mRemovedAppId >= 0) { - // If the package is not actually removed, some services need to know the - // package name affected. - if (mNewAppId >= 0 || mIsUpdate || mIsRemovedPackageSystemUpdate) { + // If a system app's updates are uninstalled the UID is not actually removed. Some + // services need to know the package name affected. + if (isReplace) { extras.putString(Intent.EXTRA_PACKAGE_NAME, mRemovedPackage); } diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index 6b57deba56b5..2016fc3093b3 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInfo; -import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; @@ -110,7 +109,7 @@ final class PackageSessionVerifier { verifyAPK(session, callback); } catch (PackageManagerException e) { String errorMessage = PackageManager.installStatusToString(e.error, e.getMessage()); - session.setSessionFailed(SessionInfo.SESSION_VERIFICATION_FAILED, errorMessage); + session.setSessionFailed(e.error, errorMessage); callback.onResult(e.error, e.getMessage()); } }); @@ -137,7 +136,7 @@ final class PackageSessionVerifier { } if (returnCode != PackageManager.INSTALL_SUCCEEDED) { String errorMessage = PackageManager.installStatusToString(returnCode, msg); - session.setSessionFailed(SessionInfo.SESSION_VERIFICATION_FAILED, errorMessage); + session.setSessionFailed(returnCode, errorMessage); callback.onResult(returnCode, msg); } else { session.setSessionReady(); @@ -220,7 +219,7 @@ final class PackageSessionVerifier { } private void onVerificationFailure(StagingManager.StagedSession session, Callback callback, - @SessionInfo.SessionErrorCode int errorCode, String errorMessage) { + int errorCode, String errorMessage) { if (!ensureActiveApexSessionIsAborted(session)) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); // Safe to ignore active apex session abortion failure since session will be marked @@ -312,7 +311,7 @@ final class PackageSessionVerifier { // Failed to get hold of StorageManager Slog.e(TAG, "Failed to get hold of StorageManager", e); throw new PackageManagerException( - SessionInfo.SESSION_UNKNOWN_ERROR, + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, "Failed to get hold of StorageManager"); } // Proactively mark session as ready before calling apexd. Although this call order @@ -350,7 +349,7 @@ final class PackageSessionVerifier { final ParseResult<SigningDetails> newResult = ApkSignatureVerifier.verify( input.reset(), apexPath, minSignatureScheme); if (newResult.isError()) { - throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Failed to parse APEX package " + apexPath + " : " + newResult.getException(), newResult.getException()); } @@ -369,7 +368,7 @@ final class PackageSessionVerifier { input.reset(), existingApexPkg.applicationInfo.sourceDir, SigningDetails.SignatureSchemeVersion.JAR); if (existingResult.isError()) { - throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir + " : " + existingResult.getException(), existingResult.getException()); } @@ -383,7 +382,7 @@ final class PackageSessionVerifier { return; } - throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "APK-container signature of APEX package " + packageName + " with version " + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not" + " compatible with the one currently installed on device"); @@ -426,11 +425,12 @@ final class PackageSessionVerifier { packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags); if (packageInfo == null) { throw new PackageManagerException( - SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Unable to generate package info: " + apexInfo.modulePath); } } catch (PackageManagerException e) { - throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e); } result.add(packageInfo); @@ -452,7 +452,7 @@ final class PackageSessionVerifier { } } throw new PackageManagerException( - SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Could not find rollback id for commit session: " + sessionId); } @@ -560,7 +560,7 @@ final class PackageSessionVerifier { try { checkActiveSessions(InstallLocationUtils.getStorageManager().supportsCheckpoint()); } catch (RemoteException e) { - throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, + throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, "Can't query fs-checkpoint status : " + e); } } @@ -576,7 +576,7 @@ final class PackageSessionVerifier { } if (!supportsCheckpoint && activeSessions > 1) { throw new PackageManagerException( - SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Cannot stage multiple sessions without checkpoint support"); } } @@ -607,13 +607,13 @@ final class PackageSessionVerifier { // will be deleted. } stagedSession.setSessionFailed( - SessionInfo.SESSION_CONFLICT, + PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Session was failed by rollback session: " + session.sessionId()); Slog.i(TAG, "Session " + stagedSession.sessionId() + " is marked failed due to " + "rollback session: " + session.sessionId()); } else if (!isRollback(session) && isRollback(stagedSession)) { throw new PackageManagerException( - SessionInfo.SESSION_CONFLICT, + PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Session was failed by rollback session: " + stagedSession.sessionId()); } @@ -636,7 +636,7 @@ final class PackageSessionVerifier { final String packageName = child.getPackageName(); if (packageName == null) { throw new PackageManagerException( - SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, "Cannot stage session " + child.sessionId() + " with package name null"); } for (StagingManager.StagedSession stagedSession : mStagedSessions) { @@ -648,14 +648,14 @@ final class PackageSessionVerifier { if (stagedSession.getCommittedMillis() < parent.getCommittedMillis()) { // Fail the session committed later when there are overlapping packages throw new PackageManagerException( - SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Package: " + packageName + " in session: " + child.sessionId() + " has been staged already by session: " + stagedSession.sessionId()); } else { stagedSession.setSessionFailed( - SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS, "Package: " + packageName + " in session: " + stagedSession.sessionId() + " has been staged already by session: " diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java index 8c49bafa06a8..7253ae4e7cb0 100644 --- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java +++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java @@ -151,7 +151,8 @@ final class PreferredActivityHelper { if (TextUtils.equals(currentPackageName, packageName)) { return false; } - final String[] callingPackages = mPm.getPackagesForUid(Binder.getCallingUid()); + final String[] callingPackages = mPm.mIPackageManager + .getPackagesForUid(Binder.getCallingUid()); if (callingPackages != null && ArrayUtils.contains(callingPackages, mPm.mRequiredPermissionControllerPackage)) { // PermissionController manages default home directly. diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 021c3db35756..6ccaae148ce6 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -40,7 +40,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.ComponentInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -113,11 +112,9 @@ import com.android.server.pm.permission.LegacyPermissionState.PermissionState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SuspendParams; import com.android.server.pm.pkg.component.ParsedComponent; import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; @@ -476,9 +473,9 @@ public final class Settings implements Watchable, Snappable { @Watched final WatchedArrayMap<String, SharedUserSetting> mSharedUsers = new WatchedArrayMap<>(); @Watched - private final WatchedArrayList<SettingBase> mAppIds; + private final AppIdSettingMap mAppIds; @Watched - private final WatchedSparseArray<SettingBase> mOtherAppIds; + private final AppIdSettingMap mOtherAppIds; // For reading/writing settings file. @Watched @@ -594,8 +591,8 @@ public final class Settings implements Watchable, Snappable { mLock = new PackageManagerTracedLock(); mPackages.putAll(pkgSettings); - mAppIds = new WatchedArrayList<>(); - mOtherAppIds = new WatchedSparseArray<>(); + mAppIds = new AppIdSettingMap(); + mOtherAppIds = new AppIdSettingMap(); mSystemDir = null; mPermissions = null; mRuntimePermissionsPersistence = null; @@ -631,8 +628,8 @@ public final class Settings implements Watchable, Snappable { mKeySetManagerService = new KeySetManagerService(mPackages); mLock = lock; - mAppIds = new WatchedArrayList<>(); - mOtherAppIds = new WatchedSparseArray<>(); + mAppIds = new AppIdSettingMap(); + mOtherAppIds = new AppIdSettingMap(); mPermissions = new LegacyPermissionSettings(lock); mRuntimePermissionsPersistence = new RuntimePermissionPersistence( runtimePermissionsPersistence, new Consumer<Integer>() { @@ -1278,7 +1275,8 @@ public final class Settings implements Watchable, Snappable { // Utility method that adds a PackageSetting to mPackages and // completes updating the shared user attributes and any restored // app link verification state - private void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) { mPackages.put(p.getPackageName(), p); if (sharedUser != null) { SharedUserSetting existingSharedUserSetting = getSharedUserSettingLPr(p); @@ -1301,7 +1299,7 @@ public final class Settings implements Watchable, Snappable { p.setAppId(sharedUser.mAppId); } - // If the we know about this user id, we have to update it as it + // If we know about this user id, we have to update it as it // has to point to the same PackageSetting instance as the package. Object userIdPs = getSettingLPr(p.getAppId()); if (sharedUser == null) { @@ -1366,20 +1364,13 @@ public final class Settings implements Watchable, Snappable { } if (appId >= Process.FIRST_APPLICATION_UID) { - int size = mAppIds.size(); - final int index = appId - Process.FIRST_APPLICATION_UID; - // fill the array until our index becomes valid - while (index >= size) { - mAppIds.add(null); - size++; - } - if (mAppIds.get(index) != null) { + if (mAppIds.get(appId) != null) { PackageManagerService.reportSettingsProblem(Log.WARN, "Adding duplicate app id: " + appId + " name=" + name); return false; } - mAppIds.set(index, obj); + mAppIds.put(appId, obj); } else { if (mOtherAppIds.get(appId) != null) { PackageManagerService.reportSettingsProblem(Log.WARN, @@ -1395,9 +1386,7 @@ public final class Settings implements Watchable, Snappable { /** Gets the setting associated with the provided App ID */ public SettingBase getSettingLPr(int appId) { if (appId >= Process.FIRST_APPLICATION_UID) { - final int size = mAppIds.size(); - final int index = appId - Process.FIRST_APPLICATION_UID; - return index < size ? mAppIds.get(index) : null; + return mAppIds.get(appId); } else { return mOtherAppIds.get(appId); } @@ -1406,9 +1395,7 @@ public final class Settings implements Watchable, Snappable { /** Unregisters the provided app ID. */ void removeAppIdLPw(int appId) { if (appId >= Process.FIRST_APPLICATION_UID) { - final int size = mAppIds.size(); - final int index = appId - Process.FIRST_APPLICATION_UID; - if (index < size) mAppIds.set(index, null); + mAppIds.remove(appId); } else { mOtherAppIds.remove(appId); } @@ -1417,9 +1404,14 @@ public final class Settings implements Watchable, Snappable { private void replaceAppIdLPw(int appId, SettingBase obj) { if (appId >= Process.FIRST_APPLICATION_UID) { - final int size = mAppIds.size(); - final int index = appId - Process.FIRST_APPLICATION_UID; - if (index < size) mAppIds.set(index, obj); + if (appId <= mAppIds.getCurrentMaxAppId()) { + mAppIds.put(appId, obj); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: calling replaceAppIdLpw to" + + " replace SettingBase at appId=" + appId + + " but nothing is replaced."); + } } else { mOtherAppIds.put(appId, obj); } @@ -4304,22 +4296,21 @@ public final class Settings implements Watchable, Snappable { /** Returns a new AppID or -1 if we could not find an available AppID to assign */ private int acquireAndRegisterNewAppIdLPw(SettingBase obj) { - // Let's be stupidly inefficient for now... - final int size = mAppIds.size(); - for (int i = mFirstAvailableUid - Process.FIRST_APPLICATION_UID; i < size; i++) { - if (mAppIds.get(i) == null) { - mAppIds.set(i, obj); - return Process.FIRST_APPLICATION_UID + i; + final int nextAvailableAppId = mAppIds.getNextAvailableAppId(); + for (int uid = mFirstAvailableUid; uid < nextAvailableAppId; uid++) { + if (mAppIds.get(uid) == null) { + mAppIds.put(uid, obj); + return uid; } } // None left? - if (size > (Process.LAST_APPLICATION_UID - Process.FIRST_APPLICATION_UID)) { + if (nextAvailableAppId > Process.LAST_APPLICATION_UID) { return -1; } - mAppIds.add(obj); - return Process.FIRST_APPLICATION_UID + size; + mAppIds.put(nextAvailableAppId, obj); + return nextAvailableAppId; } public VerifierDeviceIdentity getVerifierDeviceIdentityLPw(@NonNull Computer computer) { @@ -4354,33 +4345,6 @@ public final class Settings implements Watchable, Snappable { return getDisabledSystemPkgLPr(enabledPackageSetting.getPackageName()); } - boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, long flags, int userId) { - final PackageSetting ps = mPackages.get(componentInfo.packageName); - if (ps == null) return false; - - final PackageUserStateInternal userState = ps.readUserState(userId); - return PackageUserStateUtils.isMatch(userState, componentInfo, flags); - } - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, - long flags, int userId) { - final PackageSetting ps = mPackages.get(component.getPackageName()); - if (ps == null) return false; - - final PackageUserStateInternal userState = ps.readUserState(userId); - return PackageUserStateUtils.isMatch(userState, pkg.isSystem(), pkg.isEnabled(), component, - flags); - } - - boolean isOrphaned(String packageName) { - final PackageSetting pkg = mPackages.get(packageName); - if (pkg == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - return pkg.getInstallSource().isOrphaned; - } - int getApplicationEnabledSettingLPr(String packageName, int userId) throws PackageManager.NameNotFoundException { final PackageSetting pkg = mPackages.get(packageName); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 52a7beda43fb..43dde5cce2d6 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -28,8 +28,6 @@ import android.content.IntentFilter; import android.content.pm.ApexStagedEvent; import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionInfo.SessionErrorCode; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.StagedApexInfo; @@ -124,7 +122,7 @@ public class StagingManager { boolean containsApkSession(); boolean containsApexSession(); void setSessionReady(); - void setSessionFailed(@SessionErrorCode int errorCode, String errorMessage); + void setSessionFailed(int errorCode, String errorMessage); void setSessionApplied(); CompletableFuture<Void> installSession(); boolean hasParentSessionId(); @@ -279,7 +277,7 @@ public class StagingManager { String packageName = apexSession.getPackageName(); String errorMsg = mApexManager.getApkInApexInstallError(packageName); if (errorMsg != null) { - throw new PackageManagerException(SessionInfo.SESSION_ACTIVATION_FAILED, + throw new PackageManagerException(PackageManager.INSTALL_ACTIVATION_FAILED, "Failed to install apk-in-apex of " + packageName + " : " + errorMsg); } } @@ -392,7 +390,7 @@ public class StagingManager { revertMsg += " Reason for revert: " + reasonForRevert; } Slog.d(TAG, revertMsg); - session.setSessionFailed(SessionInfo.SESSION_UNKNOWN_ERROR, revertMsg); + session.setSessionFailed(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, revertMsg); return; } @@ -477,7 +475,7 @@ public class StagingManager { for (String apkInApex : mApexManager.getApksInApex(packageName)) { if (!apkNames.add(apkInApex)) { throw new PackageManagerException( - SessionInfo.SESSION_ACTIVATION_FAILED, + PackageManager.INSTALL_ACTIVATION_FAILED, "Package: " + packageName + " in session: " + apexSession.sessionId() + " has duplicate apk-in-apex: " + apkInApex, null); @@ -495,9 +493,7 @@ public class StagingManager { // Should be impossible throw new RuntimeException(e); } catch (ExecutionException ee) { - PackageManagerException e = (PackageManagerException) ee.getCause(); - final String errorMsg = PackageManager.installStatusToString(e.error, e.getMessage()); - throw new PackageManagerException(SessionInfo.SESSION_ACTIVATION_FAILED, errorMsg); + throw (PackageManagerException) ee.getCause(); } } @@ -651,7 +647,7 @@ public class StagingManager { // is upgrading. Fail all the sessions and exit early. for (int i = 0; i < sessions.size(); i++) { StagedSession session = sessions.get(i); - session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, + session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "Build fingerprint has changed"); } return; @@ -691,7 +687,7 @@ public class StagingManager { final ApexSessionInfo apexSession = apexSessions.get(session.sessionId()); if (apexSession == null || apexSession.isUnknown) { hasFailedApexSession = true; - session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "apexd did " + session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "apexd did " + "not know anything about a staged session supposed to be activated"); continue; } else if (isApexSessionFailed(apexSession)) { @@ -707,7 +703,7 @@ public class StagingManager { errorMsg += " Error: " + apexSession.errorMessage; } Slog.d(TAG, errorMsg); - session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, errorMsg); + session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, errorMsg); continue; } else if (apexSession.isActivated || apexSession.isSuccess) { hasAppliedApexSession = true; @@ -716,13 +712,13 @@ public class StagingManager { // Apexd did not apply the session for some unknown reason. There is no guarantee // that apexd will install it next time. Safer to proactively mark it as failed. hasFailedApexSession = true; - session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, + session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "Staged session " + session.sessionId() + " at boot didn't activate nor " + "fail. Marking it as failed anyway."); } else { Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state"); hasFailedApexSession = true; - session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, + session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "Impossible state"); } } @@ -742,7 +738,7 @@ public class StagingManager { // Session has been already failed in the loop above. continue; } - session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, + session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED, "Another apex session failed"); } return; @@ -758,7 +754,7 @@ public class StagingManager { } catch (Exception e) { Slog.e(TAG, "Staged install failed due to unhandled exception", e); onInstallationFailure(session, new PackageManagerException( - SessionInfo.SESSION_ACTIVATION_FAILED, + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, "Staged install failed due to unhandled exception: " + e), supportsCheckpoint, needsCheckpoint); } diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index f07c9eca6a15..88a298a756d4 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -15,6 +15,9 @@ "name": "CtsClassloaderSplitsHostTestCases" }, { + "name": "CtsCompilationTestCases" + }, + { "name": "CtsAppEnumerationTestCases" }, { @@ -111,9 +114,6 @@ }, { "name": "PackageManagerServiceHostTests" - }, - { - "name": "CtsCompilationTestCases" } ], "imports": [ diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index 504769064808..95482d7c7f1a 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -118,8 +118,11 @@ class UserDataPreparer { flags | StorageManager.FLAG_STORAGE_DE, false); } else { try { - Log.e(TAG, "prepareUserData failed", e); - RecoverySystem.rebootPromptAndWipeUserData(mContext, "prepareUserData failed"); + Log.wtf(TAG, "prepareUserData failed for user " + userId, e); + if (userId == UserHandle.USER_SYSTEM) { + RecoverySystem.rebootPromptAndWipeUserData(mContext, + "prepareUserData failed for system user"); + } } catch (IOException e2) { throw new RuntimeException("error rebooting into recovery", e2); } diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index eb2de6012745..0e6d5e5ed463 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -312,4 +312,12 @@ public abstract class UserManagerInternal { */ public abstract void setDefaultCrossProfileIntentFilters( @UserIdInt int parentUserId, @UserIdInt int profileUserId); + + /** + * Returns {@code true} if the system should ignore errors when preparing + * the storage directories for the user with ID {@code userId}. This will + * return {@code false} for all new users; it will only return {@code true} + * for users that already existed on-disk from an older version of Android. + */ + public abstract boolean shouldIgnorePrepareStorageErrors(int userId); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d99305d728b9..a8d24fad2598 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -211,6 +211,8 @@ public class UserManagerService extends IUserManager.Stub { private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions"; private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL = "lastRequestQuietModeEnabledCall"; + private static final String TAG_IGNORE_PREPARE_STORAGE_ERRORS = + "ignorePrepareStorageErrors"; private static final String ATTR_KEY = "key"; private static final String ATTR_VALUE_TYPE = "type"; private static final String ATTR_MULTIPLE = "m"; @@ -320,6 +322,14 @@ public class UserManagerService extends IUserManager.Stub { private long mLastRequestQuietModeEnabledMillis; + /** + * {@code true} if the system should ignore errors when preparing the + * storage directories for this user. This is {@code false} for all new + * users; it will only be {@code true} for users that already existed + * on-disk from an older version of Android. + */ + private boolean mIgnorePrepareStorageErrors; + void setLastRequestQuietModeEnabledMillis(long millis) { mLastRequestQuietModeEnabledMillis = millis; } @@ -328,6 +338,25 @@ public class UserManagerService extends IUserManager.Stub { return mLastRequestQuietModeEnabledMillis; } + boolean getIgnorePrepareStorageErrors() { + return mIgnorePrepareStorageErrors; + } + + @SuppressWarnings("AndroidFrameworkCompatChange") // This is not an app-visible API. + void setIgnorePrepareStorageErrors() { + // This method won't be called for new users. But to fully rule out + // the possibility of mIgnorePrepareStorageErrors ever being true + // for any user on any device that launched with T or later, we also + // explicitly check that DEVICE_INITIAL_SDK_INT is below T before + // honoring the request to set mIgnorePrepareStorageErrors to true. + if (Build.VERSION.DEVICE_INITIAL_SDK_INT < Build.VERSION_CODES.TIRAMISU) { + mIgnorePrepareStorageErrors = true; + return; + } + Slog.w(LOG_TAG, "Not setting mIgnorePrepareStorageErrors to true" + + " since this is a new device"); + } + void clearSeedAccountData() { seedAccountName = null; seedAccountType = null; @@ -3408,6 +3437,10 @@ public class UserManagerService extends IUserManager.Stub { serializer.endTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL); } + serializer.startTag(/* namespace */ null, TAG_IGNORE_PREPARE_STORAGE_ERRORS); + serializer.text(String.valueOf(userData.getIgnorePrepareStorageErrors())); + serializer.endTag(/* namespace */ null, TAG_IGNORE_PREPARE_STORAGE_ERRORS); + serializer.endTag(null, TAG_USER); serializer.endDocument(); @@ -3517,6 +3550,7 @@ public class UserManagerService extends IUserManager.Stub { Bundle legacyLocalRestrictions = null; RestrictionsSet localRestrictions = null; Bundle globalRestrictions = null; + boolean ignorePrepareStorageErrors = true; // default is true for old users final TypedXmlPullParser parser = Xml.resolvePullParser(is); int type; @@ -3595,6 +3629,11 @@ public class UserManagerService extends IUserManager.Stub { if (type == XmlPullParser.TEXT) { lastRequestQuietModeEnabledTimestamp = Long.parseLong(parser.getText()); } + } else if (TAG_IGNORE_PREPARE_STORAGE_ERRORS.equals(tag)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + ignorePrepareStorageErrors = Boolean.parseBoolean(parser.getText()); + } } } } @@ -3622,6 +3661,9 @@ public class UserManagerService extends IUserManager.Stub { userData.persistSeedData = persistSeedData; userData.seedAccountOptions = seedAccountOptions; userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp); + if (ignorePrepareStorageErrors) { + userData.setIgnorePrepareStorageErrors(); + } synchronized (mRestrictionsLock) { if (baseRestrictions != null) { @@ -5732,6 +5774,9 @@ public class UserManagerService extends IUserManager.Stub { pw.println(); } } + + pw.println(" Ignore errors preparing storage: " + + userData.getIgnorePrepareStorageErrors()); } private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) { @@ -6135,6 +6180,14 @@ public class UserManagerService extends IUserManager.Stub { UserManagerService.this.setDefaultCrossProfileIntentFilters( profileUserId, userTypeDetails, restrictions, parentUserId); } + + @Override + public boolean shouldIgnorePrepareStorageErrors(int userId) { + synchronized (mUsersLock) { + UserData userData = mUsers.get(userId); + return userData != null && userData.getIgnorePrepareStorageErrors(); + } + } } /** @@ -6297,7 +6350,8 @@ public class UserManagerService extends IUserManager.Stub { * {@link SecurityException} if not. */ private void verifyCallingPackage(String callingPackage, int callingUid) { - int packageUid = mPm.getPackageUid(callingPackage, 0, UserHandle.getUserId(callingUid)); + int packageUid = mPm.snapshotComputer() + .getPackageUid(callingPackage, 0, UserHandle.getUserId(callingUid)); if (packageUid != callingUid) { throw new SecurityException("Specified package " + callingPackage + " does not match the calling uid " + callingUid); diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java index 4334cbdce1f2..7423bf65c6a5 100644 --- a/services/core/java/com/android/server/pm/VerificationParams.java +++ b/services/core/java/com/android/server/pm/VerificationParams.java @@ -72,6 +72,7 @@ import android.util.Pair; import android.util.Slog; import com.android.server.DeviceIdleInternal; +import com.android.server.sdksandbox.SdkSandboxManagerLocal; import java.io.File; import java.util.ArrayList; @@ -372,9 +373,10 @@ final class VerificationParams extends HandlerParams { * Determine if we have any installed package verifiers. If we * do, then we'll defer to them to verify the packages. */ + final Computer snapshot = mPm.snapshotComputer(); final int requiredUid = requiredVerifierPackage == null ? -1 - : mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, - verifierUserId); + : snapshot.getPackageUid(requiredVerifierPackage, + MATCH_DEBUG_TRIAGED_MISSING, verifierUserId); verificationState.setRequiredVerifierUid(requiredUid); final boolean isVerificationEnabled = isVerificationEnabled(pkgLite, verifierUserId); @@ -391,8 +393,8 @@ final class VerificationParams extends HandlerParams { verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Query all live verifiers based on current user state - final ParceledListSlice<ResolveInfo> receivers = mPm.queryIntentReceivers(verification, - PACKAGE_MIME_TYPE, 0, verifierUserId); + final ParceledListSlice<ResolveInfo> receivers = mPm.queryIntentReceivers(snapshot, + verification, PACKAGE_MIME_TYPE, 0, verifierUserId); if (DEBUG_VERIFY) { Slog.d(TAG, "Found " + receivers.getList().size() + " verifiers for intent " @@ -440,9 +442,22 @@ final class VerificationParams extends HandlerParams { final long verificationTimeout = VerificationUtils.getVerificationTimeout(mPm.mContext, streaming); - final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite, + List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite, receivers.getList(), verificationState); + // Add broadcastReceiver Component to verify Sdk before run in Sdk sandbox. + if (pkgLite.isSdkLibrary) { + if (sufficientVerifiers == null) { + sufficientVerifiers = new ArrayList<>(); + } + ComponentName sdkSandboxComponentName = new ComponentName("android", + SdkSandboxManagerLocal.VERIFIER_RECEIVER); + sufficientVerifiers.add(sdkSandboxComponentName); + + // Add uid of system_server the same uid for SdkSandboxManagerService + verificationState.addSufficientVerifier(Process.myUid()); + } + DeviceIdleInternal idleController = mPm.mInjector.getLocalService(DeviceIdleInternal.class); final BroadcastOptions options = BroadcastOptions.makeBasic(); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 124df47345ae..8d1bcfcb3938 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -122,7 +122,7 @@ public class PackageInfoUtils { info.isStub = pkg.isStub(); info.coreApp = pkg.isCoreApp(); - if (!pkgSetting.hasSharedUser()) { + if (pkgSetting != null && !pkgSetting.hasSharedUser()) { // It is possible that this shared UID app has left info.sharedUserId = null; info.sharedUserLabel = 0; 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 9961ae51b92d..90842619c31e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -1403,7 +1403,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } else { if (ps.getUserStateOrDefault(userId).isInstantApp() && !bp.isInstant()) { - throw new SecurityException("Cannot grant non-ephemeral permission" + permName + throw new SecurityException("Cannot grant non-ephemeral permission " + permName + " for package " + packageName); } @@ -3532,8 +3532,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt final Boolean granted = SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission); if (granted == null) { - throw new IllegalStateException("OEM permission" + permission + " requested by package " - + pkg.getPackageName() + " must be explicitly declared granted or not"); + throw new IllegalStateException("OEM permission " + permission + + " requested by package " + pkg.getPackageName() + + " must be explicitly declared granted or not"); } return Boolean.TRUE == granted; } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index f6f9faf98c40..cbba346ce7b8 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -287,9 +287,6 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setInstallLocation(int installLocation); - /** @see R#styleable.AndroidManifest_inheritKeyStoreKeys */ - ParsingPackage setInheritKeyStoreKeys(boolean inheritKeyStoreKeys); - /** @see R#styleable.AndroidManifest_sharedUserMaxSdkVersion */ ParsingPackage setLeavingSharedUid(boolean leavingSharedUid); diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java index 67670272ef8b..1484df8ece14 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java @@ -494,7 +494,6 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, ATTRIBUTIONS_ARE_USER_VISIBLE, RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED, SDK_LIBRARY, - INHERIT_KEYSTORE_KEYS, }) public @interface Values {} private static final long EXTERNAL_STORAGE = 1L; @@ -547,9 +546,8 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, private static final long ATTRIBUTIONS_ARE_USER_VISIBLE = 1L << 47; private static final long RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED = 1L << 48; private static final long SDK_LIBRARY = 1L << 49; - private static final long INHERIT_KEYSTORE_KEYS = 1L << 50; - private static final long ENABLE_ON_BACK_INVOKED_CALLBACK = 1L << 51; - private static final long LEAVING_SHARED_UID = 1L << 52; + private static final long ENABLE_ON_BACK_INVOKED_CALLBACK = 1L << 50; + private static final long LEAVING_SHARED_UID = 1L << 51; } private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) { @@ -2394,11 +2392,6 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, } @Override - public boolean shouldInheritKeyStoreKeys() { - return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS); - } - - @Override public boolean isOnBackInvokedCallbackEnabled() { return getBoolean(Booleans.ENABLE_ON_BACK_INVOKED_CALLBACK); } @@ -2552,11 +2545,6 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, } @Override - public ParsingPackageImpl setInheritKeyStoreKeys(boolean value) { - return setBoolean(Booleans.INHERIT_KEYSTORE_KEYS, value); - } - - @Override public ParsingPackageImpl setLeavingSharedUid(boolean value) { return setBoolean(Booleans.LEAVING_SHARED_UID, value); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java index 50033f652bfd..20b1ed8d0c3e 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java @@ -352,11 +352,6 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt int getLocaleConfigRes(); /** - * @see R.styleable#AndroidManifest_inheritKeyStoreKeys - */ - boolean shouldInheritKeyStoreKeys(); - - /** * @see R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback */ boolean isOnBackInvokedCallbackEnabled(); diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 3eaca9dddcc4..112b9e070d28 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -894,9 +894,7 @@ public class ParsingPackageUtils { .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX, R.styleable.AndroidManifest_targetSandboxVersion, sa)) /* Set the global "on SD card" flag */ - .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0) - .setInheritKeyStoreKeys(bool(false, - R.styleable.AndroidManifest_inheritKeyStoreKeys, sa)); + .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0); boolean foundApp = false; final int depth = parser.getDepth(); diff --git a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java index 20b7ccd39287..92b9944b74cf 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyInternal.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyInternal.java @@ -57,6 +57,7 @@ public abstract class PermissionPolicyInternal { * prompt should be shown if the app targets S-, is currently running in a visible, focused * task, has the REVIEW_REQUIRED flag set on its implicit notification permission, and has * created at least one notification channel (even if it has since been deleted). + * * @param packageName The package whose permission is being checked * @param userId The user for whom the package is being started * @param taskId The task the notification prompt should be attached to @@ -66,10 +67,22 @@ public abstract class PermissionPolicyInternal { /** * Determine if a particular task is in the proper state to show a system-triggered permission - * prompt. A prompt can be shown if the task is focused, visible, and running. + * prompt. A prompt can be shown if the task is focused, visible, and running and + * 1. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or + * 2. The activity belongs to the same package as the one which launched the task originally, + * and the task was started with a launcher intent + * * @param taskInfo The task to be checked + * @param currPkg The package of the current top visible activity + * @param intent The intent of the current top visible activity + */ + public abstract boolean shouldShowNotificationDialogForTask(@Nullable TaskInfo taskInfo, + @Nullable String currPkg, @Nullable Intent intent); + + /** + * @return true if an intent will resolve to a permission request dialog activity */ - public abstract boolean canShowPermissionPromptForTask(@Nullable TaskInfo taskInfo); + public abstract boolean isIntentToPermissionDialog(@NonNull Intent intent); /** * @return Whether the policy is initialized for a user. diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index f2ce0d4c49d3..70ef3d364277 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -21,6 +21,8 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_NONE; +import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS; +import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; @@ -54,6 +56,8 @@ import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PermissionInfo; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -109,6 +113,7 @@ public final class PermissionPolicyService extends SystemService { private static final String SYSTEM_PKG = "android"; private static final boolean DEBUG = false; private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 60000; + private static final long ACTIVITY_START_DELAY_MS = 200; private final Object mLock = new Object(); @@ -149,6 +154,7 @@ public final class PermissionPolicyService extends SystemService { private List<String> mAppOpPermissions; private Context mContext; + private Handler mHandler; private PackageManagerInternal mPackageManagerInternal; private NotificationManagerInternal mNotificationManager; private final KeyguardManager mKeyguardManager; @@ -158,6 +164,7 @@ public final class PermissionPolicyService extends SystemService { super(context); mContext = context; + mHandler = new Handler(Looper.getMainLooper()); mPackageManager = context.getPackageManager(); mKeyguardManager = context.getSystemService(KeyguardManager.class); LocalServices.addService(PermissionPolicyInternal.class, new Internal()); @@ -1016,7 +1023,7 @@ public final class PermissionPolicyService extends SystemService { ActivityInterceptorInfo info) { String action = info.intent.getAction(); ActivityInterceptResult result = null; - if (!PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action) + if (!ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action) && !PackageManager.ACTION_REQUEST_PERMISSIONS.equals(action)) { return null; } @@ -1033,7 +1040,7 @@ public final class PermissionPolicyService extends SystemService { && !mContinueNotifGrantMessageUids.contains(info.realCallingUid)) { return result; } - if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)) { + if (ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(action)) { String otherPkg = info.intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); if (otherPkg == null || (mPackageManager.getPermissionFlags( POST_NOTIFICATIONS, otherPkg, UserHandle.of(info.userId)) @@ -1052,8 +1059,8 @@ public final class PermissionPolicyService extends SystemService { public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo, ActivityInterceptorInfo info) { super.onActivityLaunched(taskInfo, activityInfo, info); - if (!shouldShowNotificationDialogOrClearFlags(info.intent, - info.checkedOptions)) { + if (!shouldShowNotificationDialogOrClearFlags(taskInfo, + activityInfo.packageName, info.intent, info.checkedOptions, true)) { return; } UserHandle user = UserHandle.of(taskInfo.userId); @@ -1085,7 +1092,7 @@ public final class PermissionPolicyService extends SystemService { return false; } - if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(intent.getAction()) + if (ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(intent.getAction()) && (callingUid != Process.SYSTEM_UID || !SYSTEM_PKG.equals(callingPackage))) { return false; } @@ -1104,18 +1111,48 @@ public final class PermissionPolicyService extends SystemService { launchNotificationPermissionRequestDialog(packageName, user, taskId); } + @Override + public boolean isIntentToPermissionDialog(@NonNull Intent intent) { + return Objects.equals(intent.getPackage(), + mPackageManager.getPermissionControllerPackageName()) + && (Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS_FOR_OTHER) + || Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS)); + } + + @Override + public boolean shouldShowNotificationDialogForTask(TaskInfo taskInfo, String currPkg, + Intent intent) { + return shouldShowNotificationDialogOrClearFlags( + taskInfo, currPkg, intent, null, false); + } + /** - * Determine if we should show a notification dialog, or clear the REVIEW_REQUIRED flag, - * from a particular package for a particular intent. Returns true if: + * Determine if a particular task is in the proper state to show a system-triggered + * permission prompt. A prompt can be shown if the task is just starting, or the task is + * currently focused, visible, and running, and, * 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or - * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER) + * 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or + * 3. The activity belongs to the same package as the one which launched the task + * originally, and the task was started with a launcher intent + * @param taskInfo The task to be checked + * @param currPkg The package of the current top visible activity + * @param intent The intent of the current top visible activity */ - private boolean shouldShowNotificationDialogOrClearFlags(Intent intent, - ActivityOptions options) { - if ((options != null && options.isEligibleForLegacyPermissionPrompt())) { - return true; + private boolean shouldShowNotificationDialogOrClearFlags(TaskInfo taskInfo, String currPkg, + Intent intent, ActivityOptions options, boolean activityStart) { + if (intent == null || currPkg == null || taskInfo == null + || (!(taskInfo.isFocused && taskInfo.isVisible && taskInfo.isRunning) + && !activityStart)) { + return false; } + return isLauncherIntent(intent) + || (options != null && options.isEligibleForLegacyPermissionPrompt()) + || (currPkg.equals(taskInfo.baseActivity.getPackageName()) + && isLauncherIntent(taskInfo.baseIntent)); + } + + private boolean isLauncherIntent(Intent intent) { return Intent.ACTION_MAIN.equals(intent.getAction()) && intent.getCategories() != null && (intent.getCategories().contains(Intent.CATEGORY_LAUNCHER) @@ -1144,14 +1181,15 @@ public final class PermissionPolicyService extends SystemService { Intent grantPermission = mPackageManager .buildRequestPermissionsIntent(new String[] { POST_NOTIFICATIONS }); grantPermission.setAction( - PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER); + ACTION_REQUEST_PERMISSIONS_FOR_OTHER); grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName); ActivityOptions options = new ActivityOptions(new Bundle()); options.setTaskOverlay(true, false); options.setLaunchTaskId(taskId); try { - mContext.startActivityAsUser(grantPermission, options.toBundle(), user); + mHandler.postDelayed(() -> mContext.startActivityAsUser( + grantPermission, options.toBundle(), user), ACTIVITY_START_DELAY_MS); } catch (Exception e) { Log.e(LOG_TAG, "couldn't start grant permission dialog" + "for other package " + pkgName, e); @@ -1170,12 +1208,6 @@ public final class PermissionPolicyService extends SystemService { } } - @Override - public boolean canShowPermissionPromptForTask(@Nullable TaskInfo taskInfo) { - return taskInfo != null && taskInfo.isFocused && taskInfo.isVisible - && taskInfo.isRunning; - } - /** * Check if the intent action is removed for the calling package (often based on target SDK * version). If the action is removed, we'll silently cancel the activity launch. diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index dcfb8b5e7b33..a82d4eaa5b28 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManagerInternal; import android.media.AudioAttributes; import android.os.FileUtils; import android.os.Handler; @@ -525,8 +526,7 @@ public final class ShutdownThread extends Thread { shutdownTimingLog.traceBegin("ShutdownPackageManager"); metricStarted(METRIC_PM); - final PackageManagerService pm = (PackageManagerService) - ServiceManager.getService("package"); + final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); if (pm != null) { pm.shutdown(); } diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index ee0e5ba916b9..e3dcfd0c89c0 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -247,6 +247,8 @@ public class SliceManagerService extends ISliceManager.Stub { if (autoGrantPermissions != null && callingPkg != null) { // Need to own the Uri to call in with permissions to grant. enforceOwner(callingPkg, uri, userId); + // b/208232850: Needs to verify caller before granting slice access + verifyCaller(callingPkg); for (String perm : autoGrantPermissions) { if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) { int providerUser = ContentProvider.getUserIdFromUri(uri, userId); diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java index c0207f044b83..16592d764e64 100644 --- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java +++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java @@ -24,6 +24,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Environment; import android.os.FileObserver; @@ -31,7 +32,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.ResultReceiver; -import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.UserHandle; @@ -49,6 +49,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.PackageManagerService; @@ -187,10 +188,10 @@ public class DeviceStorageMonitorService extends SystemService { // when it's within 150% of the threshold, we try trimming usage // back to 200% of the threshold. if (file.getUsableSpace() < (lowBytes * 3) / 2) { - final PackageManagerService pms = (PackageManagerService) ServiceManager - .getService("package"); + final PackageManagerInternal pm = + LocalServices.getService(PackageManagerInternal.class); try { - pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0); + pm.freeStorage(vol.getFsUuid(), lowBytes * 2, 0); } catch (IOException e) { Slog.w(TAG, e); } @@ -264,10 +265,10 @@ public class DeviceStorageMonitorService extends SystemService { for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { final File file = vol.getPath(); if (file.getUsableSpace() < file.getTotalSpace() * storageThresholdPercentHigh / 100) { - final PackageManagerService pms = (PackageManagerService) ServiceManager - .getService("package"); + final PackageManagerInternal pm = + LocalServices.getService(PackageManagerInternal.class); try { - pms.freeAllAppCacheAboveQuota(vol.getFsUuid()); + pm.freeAllAppCacheAboveQuota(vol.getFsUuid()); } catch (IOException e) { Slog.w(TAG, e); } diff --git a/services/core/java/com/android/server/trust/TEST_MAPPING b/services/core/java/com/android/server/trust/TEST_MAPPING new file mode 100644 index 000000000000..be8ed67f459b --- /dev/null +++ b/services/core/java/com/android/server/trust/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "TrustTests", + "options": [ + { + "include-filter": "android.trust.test" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] + }
\ No newline at end of file diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index 58407cf7fd46..e12426b2b02c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -60,8 +60,6 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f; static final List<Step> EMPTY_STEP_LIST = new ArrayList<>(); - private final Object mLock = new Object(); - // Used within steps. public final VibrationSettings vibrationSettings; public final DeviceVibrationEffectAdapter deviceEffectAdapter; @@ -74,6 +72,11 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>(); // Signalling fields. + // Note that vibrator callback signals may happen inside vibrator HAL calls made by the + // VibrationThread, or on an external executor, so this lock should not be held for anything + // other than updating signalling state - particularly not during HAL calls or when invoking + // other callbacks that may trigger calls into the thread. + private final Object mLock = new Object(); @GuardedBy("mLock") private final IntArray mSignalVibratorsComplete; @GuardedBy("mLock") @@ -334,9 +337,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * The state update is recorded for processing on the main execution thread (VibrationThread). */ public void notifyVibratorComplete(int vibratorId) { - if (Build.IS_DEBUGGABLE) { - expectIsVibrationThread(false); - } + // HAL callbacks may be triggered directly within HAL calls, so these notifications + // could be on the VibrationThread as it calls the HAL, or some other executor later. + // Therefore no thread assertion is made here. if (DEBUG) { Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId); @@ -356,9 +359,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { * (VibrationThread). */ public void notifySyncedVibrationComplete() { - if (Build.IS_DEBUGGABLE) { - expectIsVibrationThread(false); - } + // HAL callbacks may be triggered directly within HAL calls, so these notifications + // could be on the VibrationThread as it calls the HAL, or some other executor later. + // Therefore no thread assertion is made here. if (DEBUG) { Slog.d(TAG, "Synced vibration complete reported by vibrator manager"); @@ -394,7 +397,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { int[] vibratorsToProcess = null; boolean doCancel = false; boolean doCancelImmediate = false; - // Swap out the queue of completions to process. + // Collect signals to process, but don't keep the lock while processing them. synchronized (mLock) { if (mSignalCancelImmediate) { if (mCancelledImmediately) { @@ -407,6 +410,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { doCancel = true; } if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) { + // Swap out the queue of completions to process. vibratorsToProcess = mSignalVibratorsComplete.toArray(); // makes a copy mSignalVibratorsComplete.clear(); } diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 205ea62496ff..cecc5c04dedc 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -22,6 +22,7 @@ import android.os.IBinder; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; import android.os.Trace; import android.os.WorkSource; import android.util.Slog; @@ -176,7 +177,7 @@ final class VibrationThread extends Thread { * @return true if the vibration completed, or false if waiting timed out. */ public boolean waitForThreadIdle(long maxWaitMillis) { - long now = System.currentTimeMillis(); + long now = SystemClock.elapsedRealtime(); long deadline = now + maxWaitMillis; synchronized (mLock) { while (true) { @@ -191,7 +192,7 @@ final class VibrationThread extends Thread { } catch (InterruptedException e) { Slog.w(TAG, "VibrationThread interrupted waiting to stop, continuing"); } - now = System.currentTimeMillis(); + now = SystemClock.elapsedRealtime(); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 647ca9456393..2153f5f4dd70 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -278,6 +278,7 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.gui.DropInputMode; import android.hardware.HardwareBuffer; import android.net.Uri; import android.os.Binder; @@ -782,6 +783,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean startingDisplayed; boolean startingMoved; + /** The last set {@link DropInputMode} for this activity surface. */ + @DropInputMode + private int mLastDropInputMode = DropInputMode.NONE; + /** * If it is non-null, it requires all activities who have the same starting data to be drawn * to remove the starting window. @@ -1548,6 +1553,60 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A rootTask.setHasBeenVisible(true); } } + + // Update the input mode if the embedded mode is changed. + updateUntrustedEmbeddingInputProtection(); + } + + @Override + void setSurfaceControl(SurfaceControl sc) { + super.setSurfaceControl(sc); + if (sc != null) { + mLastDropInputMode = DropInputMode.NONE; + updateUntrustedEmbeddingInputProtection(); + } + } + + /** + * Sets to drop input when obscured to activity if it is embedded in untrusted mode. + * + * Although the untrusted embedded activity should be invisible when behind other overlay, + * theoretically even if this activity is the top most, app can still move surface of activity + * below it to the top. As a result, we want to update the input mode to drop when obscured for + * all untrusted activities. + */ + private void updateUntrustedEmbeddingInputProtection() { + final SurfaceControl sc = getSurfaceControl(); + if (sc == null) { + return; + } + if (isEmbeddedInUntrustedMode()) { + // Set drop input to OBSCURED when untrusted embedded. + setDropInputMode(DropInputMode.OBSCURED); + } else { + // Reset drop input mode when this activity is not embedded in untrusted mode. + setDropInputMode(DropInputMode.NONE); + } + } + + @VisibleForTesting + void setDropInputMode(@DropInputMode int mode) { + if (mLastDropInputMode != mode && getSurfaceControl() != null) { + mLastDropInputMode = mode; + mWmService.mTransactionFactory.get() + .setDropInputMode(getSurfaceControl(), mode) + .apply(); + } + } + + private boolean isEmbeddedInUntrustedMode() { + final TaskFragment organizedTaskFragment = getOrganizedTaskFragment(); + if (organizedTaskFragment == null) { + // Not embedded. + return false; + } + // Check if trusted. + return !organizedTaskFragment.isAllowedToEmbedActivityInTrustedMode(this); } void updateAnimatingActivityRegistry() { @@ -3643,6 +3702,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } finishing = true; + + // Transfer the launch cookie to the next running activity above this in the same task. + if (mLaunchCookie != null && mState != RESUMED && task != null && !task.mInRemoveTask + && !task.isClearingToReuseTask()) { + final ActivityRecord nextCookieTarget = task.getActivity( + // Intend to only associate the same app by checking uid. + r -> r.mLaunchCookie == null && !r.finishing && r.isUid(getUid()), + this, false /* includeBoundary */, false /* traverseTopToBottom */); + if (nextCookieTarget != null) { + nextCookieTarget.mLaunchCookie = mLaunchCookie; + mLaunchCookie = null; + } + } + final TaskFragment taskFragment = getTaskFragment(); if (taskFragment != null) { final Task task = taskFragment.getTask(); @@ -5429,6 +5502,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } + // Untrusted embedded activity can be visible only if there is no other overlay window. + if (hasOverlayOverUntrustedModeEmbedded()) { + return false; + } + // Check if the activity is on a sleeping display, canTurnScreenOn will also check // keyguard visibility if (mDisplayContent.isSleeping()) { @@ -5438,6 +5516,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** + * Checks if there are any activities or other containers that belong to the same task on top of + * this activity when embedded in untrusted mode. + */ + boolean hasOverlayOverUntrustedModeEmbedded() { + if (!isEmbeddedInUntrustedMode() || getRootTask() == null) { + // The activity is not embedded in untrusted mode. + return false; + } + + // Check if there are any activities with different UID over the activity that is embedded + // in untrusted mode. Traverse bottom to top with boundary so that it will only check + // activities above this activity. + final ActivityRecord differentUidOverlayActivity = getRootTask().getActivity( + a -> a.getUid() != getUid(), this /* boundary */, false /* includeBoundary */, + false /* traverseTopToBottom */); + return differentUidOverlayActivity != null; + } + void updateVisibilityIgnoringKeyguard(boolean behindFullscreenActivity) { visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind) && showToCurrentUser(); @@ -6670,7 +6767,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is // not specified in the ActivityOptions. - if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME) { + if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME + || launchedFromUid == Process.SHELL_UID) { return false; } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { return true; @@ -6690,7 +6788,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // solid color splash screen. // Need to check sourceRecord before in case this activity is launched from service. return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM - || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME); + || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME + || launchedFromUid == Process.SHELL_UID); } private int getSplashscreenTheme(ActivityOptions options) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index fd2afd47bec4..ac121a13f308 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1816,7 +1816,8 @@ class ActivityStarter { } if (mTargetRootTask == null) { - mTargetRootTask = getLaunchRootTask(mStartActivity, mLaunchFlags, targetTask, mOptions); + mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask, + mOptions); } if (newTask) { final Task taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null) @@ -1925,7 +1926,7 @@ class ActivityStarter { } else if (mInTask != null) { return mInTask; } else { - final Task rootTask = getLaunchRootTask(mStartActivity, mLaunchFlags, null /* task */, + final Task rootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, null /* task */, mOptions); final ActivityRecord top = rootTask.getTopNonFinishingActivity(); if (top != null) { @@ -2233,7 +2234,7 @@ class ActivityStarter { if (targetTask.getRootTask() == null) { // Target root task got cleared when we all activities were removed above. // Go ahead and reset it. - mTargetRootTask = getLaunchRootTask(mStartActivity, mLaunchFlags, + mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, null /* task */, mOptions); mTargetRootTask.addChild(targetTask, !mLaunchTaskBehind /* toTop */, (mStartActivity.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); @@ -2695,10 +2696,11 @@ class ActivityStarter { // launched into the same root task. mTargetRootTask = Task.fromWindowContainerToken(mSourceRecord.mLaunchRootTask); } else { - final Task launchRootTask = - getLaunchRootTask(mStartActivity, mLaunchFlags, intentTask, mOptions); + final Task rootTask = + getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask, mOptions); + // TODO(b/184806710): #getOrCreateRootTask should never return null? mTargetRootTask = - launchRootTask != null ? launchRootTask : intentActivity.getRootTask(); + rootTask != null ? rootTask : intentActivity.getRootTask(); } } @@ -2929,7 +2931,7 @@ class ActivityStarter { return launchFlags; } - private Task getLaunchRootTask(ActivityRecord r, int launchFlags, Task task, + private Task getOrCreateRootTask(ActivityRecord r, int launchFlags, Task task, ActivityOptions aOptions) { // We are reusing a task, keep the root task! if (mReuseTask != null) { @@ -2938,7 +2940,7 @@ class ActivityStarter { final boolean onTop = (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind; - return mRootWindowContainer.getLaunchRootTask(r, aOptions, task, mSourceRootTask, onTop, + return mRootWindowContainer.getOrCreateRootTask(r, aOptions, task, mSourceRootTask, onTop, mLaunchParams, launchFlags); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index cecfccd1f836..01dfb91d12be 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -680,4 +680,15 @@ public abstract class ActivityTaskManagerInternal { /** Get the app tasks for a package */ public abstract List<ActivityManager.AppTask> getAppTasks(String pkgName, int uid); + + /** + * Determine if there exists a task which meets the criteria set by the PermissionPolicyService + * to show a system-owned permission dialog over, for a given package + * @see PermissionPolicyInternal.shouldShowNotificationDialogForTask + * + * @param pkgName The package whose activity must be top + * @param uid The uid that must have a top activity + * @return a task ID if a valid task ID is found. Otherwise, return INVALID_TASK_ID + */ + public abstract int getTaskToShowPermissionDialogOn(String pkgName, int uid); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index bfccdf97c680..b5312c4de437 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -205,8 +205,6 @@ import android.os.UpdateLock; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; -import android.os.storage.IStorageManager; -import android.os.storage.StorageManager; import android.provider.Settings; import android.service.dreams.DreamActivity; import android.service.dreams.DreamManagerInternal; @@ -4300,11 +4298,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { SystemProperties.set("persist.sys.locale", locales.get(bestLocaleIndex).toLanguageTag()); LocaleList.setDefault(locales, bestLocaleIndex); - - final Message m = PooledLambda.obtainMessage( - ActivityTaskManagerService::sendLocaleToMountDaemonMsg, this, - locales.get(bestLocaleIndex)); - mH.sendMessage(m); } mTempConfig.seq = increaseConfigurationSeqLocked(); @@ -4458,17 +4451,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Settings.System.putConfigurationForUser(resolver, config, userId); } - private void sendLocaleToMountDaemonMsg(Locale l) { - try { - IBinder service = ServiceManager.getService("mount"); - IStorageManager storageManager = IStorageManager.Stub.asInterface(service); - Log.d(TAG, "Storing locale " + l.toLanguageTag() + " for decryption UI"); - storageManager.setField(StorageManager.SYSTEM_LOCALE_KEY, l.toLanguageTag()); - } catch (RemoteException e) { - Log.e(TAG, "Error storing locale for decryption UI", e); - } - } - private void expireStartAsCallerTokenMsg(IBinder permissionToken) { mStartActivitySources.remove(permissionToken); mExpiredStartAsCallerTokens.add(permissionToken); @@ -6754,5 +6736,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } return tasks; } + + @Override + public int getTaskToShowPermissionDialogOn(String pkgName, int uid) { + synchronized (ActivityTaskManagerService.this.mGlobalLock) { + return ActivityTaskManagerService.this.mRootWindowContainer + .getTaskToShowPermissionDialogOn(pkgName, uid); + } + } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index d3d7d5e3780e..64f426187fd2 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1439,20 +1439,20 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final Rect bounds = options.getLaunchBounds(); task.setBounds(bounds); - Task launchRootTask = - mRootWindowContainer.getLaunchRootTask(null, options, task, ON_TOP); + Task targetRootTask = + mRootWindowContainer.getOrCreateRootTask(null, options, task, ON_TOP); - if (launchRootTask != currentRootTask) { - moveHomeRootTaskToFrontIfNeeded(flags, launchRootTask.getDisplayArea(), reason); - task.reparent(launchRootTask, ON_TOP, REPARENT_KEEP_ROOT_TASK_AT_FRONT, + if (targetRootTask != currentRootTask) { + moveHomeRootTaskToFrontIfNeeded(flags, targetRootTask.getDisplayArea(), reason); + task.reparent(targetRootTask, ON_TOP, REPARENT_KEEP_ROOT_TASK_AT_FRONT, !ANIMATE, DEFER_RESUME, reason); - currentRootTask = launchRootTask; + currentRootTask = targetRootTask; reparented = true; // task.reparent() should already placed the task on top, // still need moveTaskToFrontLocked() below for any transition settings. } - if (launchRootTask.shouldResizeRootTaskWithLaunchBounds()) { - launchRootTask.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME); + if (targetRootTask.shouldResizeRootTaskWithLaunchBounds()) { + targetRootTask.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME); } else { // WM resizeTask must be done after the task is moved to the correct stack, // because Task's setBounds() also updates dim layer's bounds, but that has @@ -1696,7 +1696,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { */ boolean restoreRecentTaskLocked(Task task, ActivityOptions aOptions, boolean onTop) { final Task rootTask = - mRootWindowContainer.getLaunchRootTask(null, aOptions, task, onTop); + mRootWindowContainer.getOrCreateRootTask(null, aOptions, task, onTop); final WindowContainer parent = task.getParent(); if (parent == rootTask || task == rootTask) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 718a28de6af9..d1b9a7895058 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -131,6 +131,7 @@ import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT; @@ -627,9 +628,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @VisibleForTesting SurfaceControl mInputMethodSurfaceParent; - /** The screenshot IME surface to place on the task while transitioning to the next task. */ - SurfaceControl mImeScreenshot; - private final PointerEventDispatcher mPointerEventDispatcher; private final InsetsStateController mInsetsStateController; @@ -3974,10 +3972,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // If the IME target is the input target, before it changes, prepare the IME screenshot // for the last IME target when its task is applying app transition. This is for the // better IME transition to keep IME visibility when transitioning to the next task. - if (mImeLayeringTarget != null && mImeLayeringTarget.isAnimating(PARENTS | TRANSITION, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) - && mImeLayeringTarget == mImeInputTarget) { - attachAndShowImeScreenshotOnTarget(); + if (mImeLayeringTarget != null && mImeLayeringTarget == mImeInputTarget) { + boolean nonAppImeTargetAnimatingExit = mImeLayeringTarget.mAnimatingExit + && mImeLayeringTarget.mAttrs.type != TYPE_BASE_APPLICATION + && mImeLayeringTarget.isSelfAnimating(0, ANIMATION_TYPE_WINDOW_ANIMATION); + if (mImeLayeringTarget.inAppOrRecentsTransition() || nonAppImeTargetAnimatingExit) { + showImeScreenshot(); + } } ProtoLog.i(WM_DEBUG_IME, "setInputMethodTarget %s", target); @@ -4025,71 +4026,129 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mImeControlTarget = target; } - @VisibleForTesting - void attachAndShowImeScreenshotOnTarget() { - // No need to attach screenshot if the IME target not exists or screen is off. - if (!shouldImeAttachedToApp() || !mWmService.mPolicy.isScreenOn()) { - return; + // ========== Begin of ImeScreenshot stuff ========== + /** The screenshot IME surface to place on the task while transitioning to the next task. */ + ImeScreenshot mImeScreenshot; + + static final class ImeScreenshot { + private WindowState mImeTarget; + private SurfaceControl.Builder mSurfaceBuilder; + private SurfaceControl mImeSurface; + + ImeScreenshot(SurfaceControl.Builder surfaceBuilder, @NonNull WindowState imeTarget) { + mSurfaceBuilder = surfaceBuilder; + mImeTarget = imeTarget; } - final SurfaceControl.Transaction t = getPendingTransaction(); - // Prepare IME screenshot for the target if it allows to attach into. - if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) { - final Task task = mImeLayeringTarget.getTask(); + WindowState getImeTarget() { + return mImeTarget; + } + + private SurfaceControl createImeSurface(SurfaceControl.ScreenshotHardwareBuffer b, + Transaction t) { + final HardwareBuffer buffer = b.getHardwareBuffer(); + if (DEBUG_INPUT_METHOD) { + Slog.d(TAG, "create IME snapshot for " + + mImeTarget + ", buff width=" + buffer.getWidth() + + ", height=" + buffer.getHeight()); + } + final WindowState imeWindow = mImeTarget.getDisplayContent().mInputMethodWindow; + final ActivityRecord activity = mImeTarget.mActivityRecord; + final SurfaceControl imeParent = mImeTarget.mAttrs.type == TYPE_BASE_APPLICATION + ? activity.getSurfaceControl() + : mImeTarget.getSurfaceControl(); + final SurfaceControl imeSurface = mSurfaceBuilder + .setName("IME-snapshot-surface") + .setBLASTLayer() + .setFormat(buffer.getFormat()) + // Attaching IME snapshot to the associated IME layering target on the + // activity when: + // - The target is activity main window: attaching on top of the activity. + // - The target is non-activity main window (e.g. activity overlay or + // dialog-themed activity): attaching on top of the target since the layer has + // already above the activity. + .setParent(imeParent) + .setCallsite("DisplayContent.attachAndShowImeScreenshotOnTarget") + .build(); + // Make IME snapshot as trusted overlay + InputMonitor.setTrustedOverlayInputInfo(imeSurface, t, imeWindow.getDisplayId(), + "IME-snapshot-surface"); + t.setBuffer(imeSurface, buffer); + t.setColorSpace(activity.mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB)); + t.setLayer(imeSurface, 1); + + final Point surfacePosition = new Point( + imeWindow.getFrame().left - mImeTarget.getFrame().left, + imeWindow.getFrame().top - mImeTarget.getFrame().top); + if (imeParent == activity.getSurfaceControl()) { + t.setPosition(imeSurface, surfacePosition.x, surfacePosition.y); + } else { + surfacePosition.offset(mImeTarget.mAttrs.surfaceInsets.left, + mImeTarget.mAttrs.surfaceInsets.top); + t.setPosition(imeSurface, surfacePosition.x, surfacePosition.y); + } + return imeSurface; + } + + private void removeImeSurface(Transaction t) { + if (mImeSurface != null) { + if (DEBUG_INPUT_METHOD) Slog.d(TAG, "remove IME snapshot"); + t.remove(mImeSurface); + mImeSurface = null; + } + } + + void attachAndShow(Transaction t) { + final DisplayContent dc = mImeTarget.getDisplayContent(); + // Prepare IME screenshot for the target if it allows to attach into. + final Task task = mImeTarget.getTask(); // Re-new the IME screenshot when it does not exist or the size changed. - final boolean renewImeSurface = mImeScreenshot == null - || mImeScreenshot.getWidth() != mInputMethodWindow.getFrame().width() - || mImeScreenshot.getHeight() != mInputMethodWindow.getFrame().height(); + final boolean renewImeSurface = mImeSurface == null + || mImeSurface.getWidth() != dc.mInputMethodWindow.getFrame().width() + || mImeSurface.getHeight() != dc.mInputMethodWindow.getFrame().height(); if (task != null && !task.isActivityTypeHomeOrRecents()) { SurfaceControl.ScreenshotHardwareBuffer imeBuffer = renewImeSurface - ? mWmService.mTaskSnapshotController.snapshotImeFromAttachedTask(task) + ? dc.mWmService.mTaskSnapshotController.snapshotImeFromAttachedTask(task) : null; if (imeBuffer != null) { // Remove the last IME surface when the surface needs to renew. - removeImeSurfaceImmediately(); - mImeScreenshot = createImeSurface(imeBuffer, t); + removeImeSurface(t); + mImeSurface = createImeSurface(imeBuffer, t); + } + } + final boolean isValidSnapshot = mImeSurface != null && mImeSurface.isValid(); + // Showing the IME screenshot if the target has already in app transition stage. + // Note that if the current IME insets is not showing, no need to show IME screenshot + // to reflect the true IME insets visibility and the app task layout as possible. + if (isValidSnapshot + && dc.getInsetsStateController().getImeSourceProvider().isImeShowing()) { + if (DEBUG_INPUT_METHOD) { + Slog.d(TAG, "show IME snapshot, ime target=" + mImeTarget); } + t.show(mImeSurface); + } else if (!isValidSnapshot) { + removeImeSurface(t); } } - final boolean isValidSnapshot = mImeScreenshot != null && mImeScreenshot.isValid(); - // Showing the IME screenshot if the target has already in app transition stage. - // Note that if the current IME insets is not showing, no need to show IME screenshot - // to reflect the true IME insets visibility and the app task layout as possible. - if (isValidSnapshot && getInsetsStateController().getImeSourceProvider().isImeShowing()) { - if (DEBUG_INPUT_METHOD) { - Slog.d(TAG, "show IME snapshot, ime target=" + mImeLayeringTarget); - } - t.show(mImeScreenshot); - } else if (!isValidSnapshot) { - removeImeSurfaceImmediately(); + void detach(Transaction t) { + removeImeSurface(t); } } - @VisibleForTesting - SurfaceControl createImeSurface(SurfaceControl.ScreenshotHardwareBuffer imeBuffer, - Transaction t) { - final HardwareBuffer buffer = imeBuffer.getHardwareBuffer(); - if (DEBUG_INPUT_METHOD) Slog.d(TAG, "create IME snapshot for " - + mImeLayeringTarget + ", buff width=" + buffer.getWidth() - + ", height=" + buffer.getHeight()); - final ActivityRecord activity = mImeLayeringTarget.mActivityRecord; - final SurfaceControl imeSurface = mWmService.mSurfaceControlFactory.apply(null) - .setName("IME-snapshot-surface") - .setBLASTLayer() - .setFormat(buffer.getFormat()) - .setParent(activity.getSurfaceControl()) - .setCallsite("DisplayContent.attachAndShowImeScreenshotOnTarget") - .build(); - // Make IME snapshot as trusted overlay - InputMonitor.setTrustedOverlayInputInfo(imeSurface, t, getDisplayId(), - "IME-snapshot-surface"); - t.setBuffer(imeSurface, buffer); - t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB)); - t.setLayer(imeSurface, 1); - t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left, - mInputMethodWindow.getDisplayFrame().top); - return imeSurface; + private void attachAndShowImeScreenshotOnTarget() { + // No need to attach screenshot if the IME target not exists or screen is off. + if (!shouldImeAttachedToApp() || !mWmService.mPolicy.isScreenOn()) { + return; + } + + final SurfaceControl.Transaction t = getPendingTransaction(); + // Prepare IME screenshot for the target if it allows to attach into. + if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) { + mImeScreenshot = new ImeScreenshot( + mWmService.mSurfaceControlFactory.apply(null), mImeLayeringTarget); + mImeScreenshot.attachAndShow(t); + } } /** @@ -4111,8 +4170,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void removeImeScreenshotIfPossible() { if (mImeLayeringTarget == null || mImeLayeringTarget.mAttrs.type != TYPE_APPLICATION_STARTING - && !mImeLayeringTarget.isAnimating(PARENTS | TRANSITION, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) { + && !mImeLayeringTarget.inAppOrRecentsTransition()) { removeImeSurfaceImmediately(); } } @@ -4120,11 +4178,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Removes the IME screenshot immediately. */ void removeImeSurfaceImmediately() { if (mImeScreenshot != null) { - if (DEBUG_INPUT_METHOD) Slog.d(TAG, "remove IME snapshot"); - getSyncTransaction().remove(mImeScreenshot); + mImeScreenshot.detach(getSyncTransaction()); mImeScreenshot = null; } } + // ========== End of ImeScreenshot stuff ========== /** * The IME input target is the window which receives input from IME. It is also a candidate @@ -4433,7 +4491,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * hierarchy. */ void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) { - if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) { + if (mImeScreenshot != null && (wc == mImeScreenshot.getImeTarget() + || wc.getWindow(w -> w == mImeScreenshot.getImeTarget()) != null) + && (type & WindowState.EXIT_ANIMATING_TYPES) != 0) { removeImeSurfaceImmediately(); } } diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index c55af9b6286e..7bf150b18e9b 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -443,6 +443,13 @@ class KeyguardController { || !mWindowManager.isKeyguardSecure(mService.getCurrentUserId()); } + /** + * @return Whether the dream activity is on top of default display. + */ + boolean isShowingDream() { + return getDisplayState(DEFAULT_DISPLAY).mShowingDream; + } + private void dismissMultiWindowModeForTaskIfNeeded(int displayId, @Nullable Task currentTaskControllingOcclusion) { // TODO(b/113840485): Handle docked stack for individual display. @@ -501,6 +508,7 @@ class KeyguardController { private boolean mKeyguardGoingAway; private boolean mDismissalRequested; private boolean mOccluded; + private boolean mShowingDream; private ActivityRecord mTopOccludesActivity; private ActivityRecord mDismissingKeyguardActivity; @@ -536,6 +544,7 @@ class KeyguardController { mRequestDismissKeyguard = false; mOccluded = false; + mShowingDream = false; mTopOccludesActivity = null; mDismissingKeyguardActivity = null; @@ -570,9 +579,9 @@ class KeyguardController { } } - final boolean dreaming = display.getDisplayPolicy().isShowingDreamLw() && (top != null + mShowingDream = display.getDisplayPolicy().isShowingDreamLw() && (top != null && top.getActivityType() == ACTIVITY_TYPE_DREAM); - mOccluded = dreaming || occludedByActivity; + mOccluded = mShowingDream || occludedByActivity; mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity && !mOccluded && mDismissingKeyguardActivity != null diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 7bddb620c94d..f3713eb7f474 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -19,20 +19,46 @@ package com.android.server.wm; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; -import android.util.ArraySet; +import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.view.Display; import android.view.Display.Mode; import android.view.DisplayInfo; +import java.util.HashMap; + /** * Policy to select a lower refresh rate for the display if applicable. */ class RefreshRatePolicy { + class PackageRefreshRate { + private final HashMap<String, RefreshRateRange> mPackages = new HashMap<>(); + + public void add(String s, float minRefreshRate, float maxRefreshRate) { + float minSupportedRefreshRate = + Math.max(RefreshRatePolicy.this.mMinSupportedRefreshRate, minRefreshRate); + float maxSupportedRefreshRate = + Math.min(RefreshRatePolicy.this.mMaxSupportedRefreshRate, maxRefreshRate); + + mPackages.put(s, + new RefreshRateRange(minSupportedRefreshRate, maxSupportedRefreshRate)); + } + + public RefreshRateRange get(String s) { + return mPackages.get(s); + } + + public void remove(String s) { + mPackages.remove(s); + } + } + private final Mode mLowRefreshRateMode; - private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>(); + private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate(); private final HighRefreshRateDenylist mHighRefreshRateDenylist; private final WindowManagerService mWmService; + private float mMinSupportedRefreshRate; + private float mMaxSupportedRefreshRate; /** * The following constants represent priority of the window. SF uses this information when @@ -70,7 +96,12 @@ class RefreshRatePolicy { Mode mode = displayInfo.getDefaultMode(); float[] refreshRates = displayInfo.getDefaultRefreshRates(); float bestRefreshRate = mode.getRefreshRate(); + mMinSupportedRefreshRate = bestRefreshRate; + mMaxSupportedRefreshRate = bestRefreshRate; for (int i = refreshRates.length - 1; i >= 0; i--) { + mMinSupportedRefreshRate = Math.min(mMinSupportedRefreshRate, refreshRates[i]); + mMaxSupportedRefreshRate = Math.max(mMaxSupportedRefreshRate, refreshRates[i]); + if (refreshRates[i] >= 60f && refreshRates[i] < bestRefreshRate) { bestRefreshRate = refreshRates[i]; } @@ -78,12 +109,13 @@ class RefreshRatePolicy { return displayInfo.findDefaultModeByRefreshRate(bestRefreshRate); } - void addNonHighRefreshRatePackage(String packageName) { - mNonHighRefreshRatePackages.add(packageName); + void addRefreshRateRangeForPackage(String packageName, + float minRefreshRate, float maxRefreshRate) { + mNonHighRefreshRatePackages.add(packageName, minRefreshRate, maxRefreshRate); mWmService.requestTraversal(); } - void removeNonHighRefreshRatePackage(String packageName) { + void removeRefreshRateRangeForPackage(String packageName) { mNonHighRefreshRatePackages.remove(packageName); mWmService.requestTraversal(); } @@ -172,8 +204,9 @@ class RefreshRatePolicy { // If app is using Camera, we set both the min and max refresh rate to the camera's // preferred refresh rate to make sure we don't end up with a refresh rate lower // than the camera capture rate, which will lead to dropping camera frames. - if (mNonHighRefreshRatePackages.contains(packageName)) { - return mLowRefreshRateMode.getRefreshRate(); + RefreshRateRange range = mNonHighRefreshRatePackages.get(packageName); + if (range != null) { + return range.min; } return 0; @@ -192,8 +225,9 @@ class RefreshRatePolicy { final String packageName = w.getOwningPackage(); // If app is using Camera, force it to default (lower) refresh rate. - if (mNonHighRefreshRatePackages.contains(packageName)) { - return mLowRefreshRateMode.getRefreshRate(); + RefreshRateRange range = mNonHighRefreshRatePackages.get(packageName); + if (range != null) { + return range.max; } return 0; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index f65b17db1e10..235533341433 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -149,6 +149,7 @@ import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.am.AppTimeTracker; import com.android.server.am.UserState; +import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.WindowManagerPolicy; import java.io.FileDescriptor; @@ -2727,9 +2728,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return false; } - Task getLaunchRootTask(@Nullable ActivityRecord r, @Nullable ActivityOptions options, + Task getOrCreateRootTask(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable Task candidateTask, boolean onTop) { - return getLaunchRootTask(r, options, candidateTask, null /* sourceTask */, onTop, + return getOrCreateRootTask(r, options, candidateTask, null /* sourceTask */, onTop, null /* launchParams */, 0 /* launchFlags */); } @@ -2744,9 +2745,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * @param launchFlags The launch flags for this launch. * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid} * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid} - * @return The root task to use for the launch or INVALID_TASK_ID. + * @return The root task to use for the launch. */ - Task getLaunchRootTask(@Nullable ActivityRecord r, + Task getOrCreateRootTask(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable Task candidateTask, @Nullable Task sourceTask, boolean onTop, @Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags) { @@ -3235,12 +3236,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (aOptions != null) { // Resolve the root task the task should be placed in now based on options // and reparent if needed. - final Task launchRootTask = - getLaunchRootTask(null, aOptions, task, onTop); - if (launchRootTask != null && task.getRootTask() != launchRootTask) { + final Task targetRootTask = + getOrCreateRootTask(null, aOptions, task, onTop); + if (targetRootTask != null && task.getRootTask() != targetRootTask) { final int reparentMode = onTop ? REPARENT_MOVE_ROOT_TASK_TO_FRONT : REPARENT_LEAVE_ROOT_TASK_IN_PLACE; - task.reparent(launchRootTask, onTop, reparentMode, ANIMATE, DEFER_RESUME, + task.reparent(targetRootTask, onTop, reparentMode, ANIMATE, DEFER_RESUME, "anyTaskForId"); } } @@ -3332,6 +3333,36 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } /** + * Iterate over all task fragments, to see if there exists one that meets the + * PermissionPolicyService's criteria to show a permission dialog. + */ + public int getTaskToShowPermissionDialogOn(String pkgName, int uid) { + PermissionPolicyInternal pPi = mService.getPermissionPolicyInternal(); + if (pPi == null) { + return INVALID_TASK_ID; + } + + final int[] validTaskId = {INVALID_TASK_ID}; + forAllLeafTaskFragments(fragment -> { + ActivityRecord record = fragment.getActivity((r) -> { + // skip hidden (or about to hide) apps, or the permission dialog + return r.canBeTopRunning() && r.isVisibleRequested() + && !pPi.isIntentToPermissionDialog(r.intent); + }); + if (record != null && record.isUid(uid) + && Objects.equals(pkgName, record.packageName) + && pPi.shouldShowNotificationDialogForTask(record.getTask().getTaskInfo(), + pkgName, record.intent)) { + validTaskId[0] = record.getTask().mTaskId; + return true; + } + return false; + }); + + return validTaskId[0]; + } + + /** * Dumps the activities matching the given {@param name} in the either the focused root task * or all visible root tasks if {@param dumpVisibleRootTasksOnly} is true. */ diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 3d6d1825adc7..1ec191ed7c05 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -57,8 +57,6 @@ class RunningTasks { private ArraySet<Integer> mProfileIds; private boolean mAllowed; private boolean mFilterOnlyVisibleRecents; - private Task mTopDisplayFocusRootTask; - private Task mTopDisplayAdjacentTask; private RecentTasks mRecentTasks; private boolean mKeepIntentExtra; @@ -78,17 +76,9 @@ class RunningTasks { mAllowed = (flags & FLAG_ALLOWED) == FLAG_ALLOWED; mFilterOnlyVisibleRecents = (flags & FLAG_FILTER_ONLY_VISIBLE_RECENTS) == FLAG_FILTER_ONLY_VISIBLE_RECENTS; - mTopDisplayFocusRootTask = root.getTopDisplayFocusedRootTask(); mRecentTasks = root.mService.getRecentTasks(); mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA; - if (mTopDisplayFocusRootTask != null - && mTopDisplayFocusRootTask.getAdjacentTaskFragment() != null) { - mTopDisplayAdjacentTask = mTopDisplayFocusRootTask.getAdjacentTaskFragment().asTask(); - } else { - mTopDisplayAdjacentTask = null; - } - final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this, PooledLambda.__(Task.class)); root.forAllLeafTasks(c, false); @@ -132,18 +122,15 @@ class RunningTasks { return; } - final Task rootTask = task.getRootTask(); - if (rootTask == mTopDisplayFocusRootTask && rootTask.getTopMostTask() == task) { - // For the focused top root task, update the last root task active time so that it - // can be used to determine the order of the tasks (it may not be set for newly - // created tasks) + if (task.isVisible()) { + // For the visible task, update the last active time so that it can be used to determine + // the order of the tasks (it may not be set for newly created tasks) task.touchActiveTime(); - } else if (rootTask == mTopDisplayAdjacentTask && rootTask.getTopMostTask() == task) { - // The short-term workaround for launcher could get suitable running task info in - // split screen. - task.touchActiveTime(); - // TreeSet doesn't allow same value and make sure this task is lower than focus one. - task.lastActiveTime--; + if (!task.isFocused()) { + // TreeSet doesn't allow the same value and make sure this task is lower than the + // focused one. + task.lastActiveTime -= mTmpSortedSet.size(); + } } mTmpSortedSet.add(task); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 923ac41cec77..8edb3137c416 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1523,7 +1523,7 @@ class Task extends TaskFragment { mTaskSupervisor.removeTask(this, false /* killProcess */, !REMOVE_FROM_RECENTS, reason); } - } else if (!mReuseTask && !mCreatedByOrganizer) { + } else if (!mReuseTask && shouldRemoveSelfOnLastChildRemoval()) { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse // or created by task organizer. if (!isRootTask()) { @@ -2036,8 +2036,10 @@ class Task extends TaskFragment { Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds(); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { - // Use empty bounds to indicate "fill parent". - outOverrideBounds.setEmpty(); + if (!mCreatedByOrganizer) { + // Use empty bounds to indicate "fill parent". + outOverrideBounds.setEmpty(); + } // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if // the parent or display is smaller than the size, the content may be cropped. return; @@ -4280,7 +4282,7 @@ class Task extends TaskFragment { /** * @return true if the task is currently focused. */ - private boolean isFocused() { + boolean isFocused() { if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) { return false; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index afc3087f4ee9..3985cbcaa419 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -516,12 +516,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) */ boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) { - if ((a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) - == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) { - return true; - } + return isAllowedToEmbedActivityInUntrustedMode(a) + || isAllowedToEmbedActivityInTrustedMode(a); + } - return isAllowedToEmbedActivityInTrustedMode(a); + boolean isAllowedToEmbedActivityInUntrustedMode(@NonNull ActivityRecord a) { + return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING) + == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; } /** @@ -531,7 +532,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { * <li>the activity has declared the organizer host as trusted explicitly via known * certificate.</li> */ - private boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) { + boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) { if (UserHandle.getAppId(mTaskFragmentOrganizerUid) == SYSTEM_UID) { // The system is trusted to embed other apps securely and for all users. return true; @@ -2303,6 +2304,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { mMinHeight = minHeight; } + boolean shouldRemoveSelfOnLastChildRemoval() { + return !mCreatedByOrganizer || mIsRemovalRequested; + } + @Override void removeChild(WindowContainer child) { removeChild(child, true /* removeSelfIfPossible */); @@ -2318,7 +2323,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { mBackScreenshots.remove(r.mActivityComponent.flattenToString()); } } - if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) { + if (removeSelfIfPossible && shouldRemoveSelfOnLastChildRemoval() && !hasChild()) { removeImmediately("removeLastChild " + child); } } @@ -2336,13 +2341,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { return; } mIsRemovalRequested = true; - forAllActivities(r -> { - if (withTransition) { + // The task order may be changed by finishIfPossible() for adjusting focus if there are + // nested tasks, so add all activities into a list to avoid missed removals. + final ArrayList<ActivityRecord> removingActivities = new ArrayList<>(); + forAllActivities((Consumer<ActivityRecord>) removingActivities::add); + for (int i = removingActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = removingActivities.get(i); + if (withTransition && r.isVisible()) { r.finishIfPossible(reason, false /* oomAdj */); } else { r.destroyIfPossible(reason); } - }); + } } void setDelayLastActivityRemoval(boolean delay) { diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 331f1242da1d..ff5bfbee61f4 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -800,17 +800,24 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - DisplayContent dc = mService.mWindowManager.mRoot + final DisplayContent dc = mService.mWindowManager.mRoot .getDisplayContent(displayId); - if (dc == null || dc.getImeTarget(IME_TARGET_LAYERING) == null) { + if (dc == null) { + return null; + } + + final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING); + if (imeLayeringTarget == null || imeLayeringTarget.getWindow() == null) { return null; } + // Avoid WindowState#getRootTask() so we don't attribute system windows to a task. - final Task task = dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getTask(); + final Task task = imeLayeringTarget.getWindow().asTask(); if (task == null) { return null; } - return task.getRootTask().mRemoteToken.toWindowContainerToken(); + + return task.mRemoteToken.toWindowContainerToken(); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b096bf1412cb..4c23f3991c58 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -574,6 +574,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe false /* disableImeIcon */); } } + dc.removeImeSurfaceImmediately(); dc.handleCompleteDeferredRemoval(); } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 043623392546..8840cd557de6 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -61,7 +61,7 @@ class TransitionController { /** Whether to use shell-transitions rotation instead of fixed-rotation. */ private static final boolean SHELL_TRANSITIONS_ROTATION = - SystemProperties.getBoolean("persist.debug.shell_transit_rotate", false); + SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */ private static final int DEFAULT_TIMEOUT_MS = 5000; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 9585a4b93a97..0a3c3f049f43 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -725,17 +725,18 @@ public abstract class WindowManagerInternal { public abstract void hideIme(IBinder imeTargetWindowToken, int displayId); /** - * Tell window manager about a package that should not be running with high refresh rate - * setting until removeNonHighRefreshRatePackage is called for the same package. + * Tell window manager about a package that should be running with a restricted range of + * refresh rate setting until removeRefreshRateRangeForPackage is called for the same package. * * This must not be called again for the same package. */ - public abstract void addNonHighRefreshRatePackage(@NonNull String packageName); + public abstract void addRefreshRateRangeForPackage(@NonNull String packageName, + float minRefreshRate, float maxRefreshRate); /** * Tell window manager to stop constraining refresh rate for the given package. */ - public abstract void removeNonHighRefreshRatePackage(@NonNull String packageName); + public abstract void removeRefreshRateRangeForPackage(@NonNull String packageName); /** * Checks if the device supports touch or faketouch. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 49d9b65dde68..6718235e8982 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -406,7 +406,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Use WMShell for app transition. */ - public static final String ENABLE_SHELL_TRANSITIONS = "persist.debug.shell_transit"; + public static final String ENABLE_SHELL_TRANSITIONS = "persist.wm.debug.shell_transit"; /** * @see #ENABLE_SHELL_TRANSITIONS @@ -3233,8 +3233,8 @@ public class WindowManagerService extends IWindowManager.Stub if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) { throw new SecurityException("Requires CONTROL_KEYGUARD permission"); } - if (mAtmService.isDreaming()) { - mAtmService.mTaskSupervisor.wakeUp("dismissKeyguard"); + if (mAtmService.mKeyguardController.isShowingDream()) { + mAtmService.mTaskSupervisor.wakeUp("leaveDream"); } synchronized (mGlobalLock) { mPolicy.dismissKeyguardLw(callback, message); @@ -7969,18 +7969,20 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void addNonHighRefreshRatePackage(@NonNull String packageName) { + public void addRefreshRateRangeForPackage(@NonNull String packageName, + float minRefreshRate, float maxRefreshRate) { synchronized (mGlobalLock) { mRoot.forAllDisplays(dc -> dc.getDisplayPolicy().getRefreshRatePolicy() - .addNonHighRefreshRatePackage(packageName)); + .addRefreshRateRangeForPackage( + packageName, minRefreshRate, maxRefreshRate)); } } @Override - public void removeNonHighRefreshRatePackage(@NonNull String packageName) { + public void removeRefreshRateRangeForPackage(@NonNull String packageName) { synchronized (mGlobalLock) { mRoot.forAllDisplays(dc -> dc.getDisplayPolicy().getRefreshRatePolicy() - .removeNonHighRefreshRatePackage(packageName)); + .removeRefreshRateRangeForPackage(packageName)); } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index ce27d739e1b1..81344ac31108 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -701,6 +701,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), errorCallbackToken, convertStartFailureToThrowable(result, activityIntent)); + } else { + effects |= TRANSACT_EFFECTS_LIFECYCLE; } break; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index 2090ab367438..2f5ab0b31332 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -307,8 +307,8 @@ class ActiveAdmin { public boolean mAdminCanGrantSensorsPermissions; public boolean mPreferentialNetworkServiceEnabled = DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT; - public PreferentialNetworkServiceConfig mPreferentialNetworkServiceConfig = - PreferentialNetworkServiceConfig.DEFAULT; + public List<PreferentialNetworkServiceConfig> mPreferentialNetworkServiceConfigs = + List.of(PreferentialNetworkServiceConfig.DEFAULT); private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true; boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 5388f171854d..3d40f48f244d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -133,7 +133,6 @@ import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; -import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; @@ -141,6 +140,7 @@ import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.provider.Telephony.Carriers.DPC_URI; import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; +import static android.provider.Telephony.Carriers.INVALID_APN_ID; import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB; @@ -3357,14 +3357,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updatePermissionPolicyCache(userId); updateAdminCanGrantSensorsPermissionCache(userId); - final PreferentialNetworkServiceConfig preferentialNetworkServiceConfig; + final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs; synchronized (getLockObject()) { ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId); - preferentialNetworkServiceConfig = owner != null - ? owner.mPreferentialNetworkServiceConfig - : PreferentialNetworkServiceConfig.DEFAULT; + preferentialNetworkServiceConfigs = owner != null + ? owner.mPreferentialNetworkServiceConfigs + : List.of(PreferentialNetworkServiceConfig.DEFAULT); } - updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfig); + updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs); startOwnerService(userId, "start-user"); } @@ -3381,7 +3381,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override void handleStopUser(int userId) { - updateNetworkPreferenceForUser(userId, PreferentialNetworkServiceConfig.DEFAULT); + updateNetworkPreferenceForUser(userId, List.of(PreferentialNetworkServiceConfig.DEFAULT)); stopOwnerService(userId, "stop-user"); } @@ -12258,87 +12258,50 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void setPreferentialNetworkServiceEnabled(boolean enabled) { + public void setPreferentialNetworkServiceConfigs( + List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) { if (!mHasFeature) { return; } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isProfileOwner(caller), - "Caller is not profile owner;" - + " only profile owner may control the preferential network service"); - synchronized (getLockObject()) { - final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked( - caller.getUserId()); - if (requiredAdmin != null - && requiredAdmin.mPreferentialNetworkServiceEnabled != enabled) { - requiredAdmin.mPreferentialNetworkServiceEnabled = enabled; - saveSettingsLocked(caller.getUserId()); - } - } - updateNetworkPreferenceForUser(caller.getUserId(), enabled); - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED) - .setBoolean(enabled) - .write(); - } - - @Override - public boolean isPreferentialNetworkServiceEnabled(int userHandle) { - if (!mHasFeature) { - return false; - } - - final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isProfileOwner(caller), - "Caller is not profile owner"); - synchronized (getLockObject()) { - final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle); - if (requiredAdmin != null) { - return requiredAdmin.mPreferentialNetworkServiceEnabled; - } else { - return false; - } - } - } - - @Override - public void setPreferentialNetworkServiceConfig( - PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) { - if (!mHasFeature) { - return; - } - final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isProfileOwner(caller), - "Caller is not profile owner;" - + " only profile owner may control the preferential network service"); + Preconditions.checkCallAuthorization(isProfileOwner(caller) + || isDefaultDeviceOwner(caller), + "Caller is not profile owner or device owner;" + + " only profile owner or device owner may control the preferential" + + " network service"); synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked( caller.getUserId()); - if (!requiredAdmin.mPreferentialNetworkServiceConfig.equals( - preferentialNetworkServiceConfig)) { - requiredAdmin.mPreferentialNetworkServiceConfig = preferentialNetworkServiceConfig; + if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals( + preferentialNetworkServiceConfigs)) { + requiredAdmin.mPreferentialNetworkServiceConfigs = + new ArrayList<>(preferentialNetworkServiceConfigs); saveSettingsLocked(caller.getUserId()); } } - updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfig); + updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED) - .setBoolean(preferentialNetworkServiceConfig.isEnabled()) + .setBoolean(preferentialNetworkServiceConfigs + .stream().anyMatch(c -> c.isEnabled())) .write(); } @Override - public PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() { + public List<PreferentialNetworkServiceConfig> getPreferentialNetworkServiceConfigs() { if (!mHasFeature) { - return PreferentialNetworkServiceConfig.DEFAULT; + return List.of(PreferentialNetworkServiceConfig.DEFAULT); } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isProfileOwner(caller), - "Caller is not profile owner"); + Preconditions.checkCallAuthorization(isProfileOwner(caller) + || isDefaultDeviceOwner(caller), + "Caller is not profile owner or device owner;" + + " only profile owner or device owner may retrieve the preferential" + + " network service configurations"); synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(caller.getUserId()); - return requiredAdmin.mPreferentialNetworkServiceConfig; + return requiredAdmin.mPreferentialNetworkServiceConfigs; } } @@ -16459,7 +16422,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); + if (apnSetting.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) { + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwner(caller)); + } else { + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); + } TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); if (tm != null) { @@ -16467,7 +16435,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { () -> tm.addDevicePolicyOverrideApn(mContext, apnSetting)); } else { Slogf.w(LOG_TAG, "TelephonyManager is null when trying to add override apn"); - return Telephony.Carriers.INVALID_APN_ID; + return INVALID_APN_ID; } } @@ -16480,7 +16448,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); + ApnSetting apn = getApnSetting(apnId); + if (apn != null && apn.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE + && apnSetting.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) { + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwner(caller)); + } else { + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); + } if (apnId < 0) { return false; @@ -16502,7 +16477,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); + ApnSetting apn = getApnSetting(apnId); + if (apn != null && apn.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) { + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwner(caller)); + } else { + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); + } return removeOverrideApnUnchecked(apnId); } @@ -16516,6 +16497,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return numDeleted > 0; } + private ApnSetting getApnSetting(int apnId) { + if (apnId < 0) { + return null; + } + ApnSetting apnSetting = null; + Cursor cursor = mInjector.binderWithCleanCallingIdentity( + () -> mContext.getContentResolver().query( + Uri.withAppendedPath(DPC_URI, Integer.toString(apnId)), null, null, null, + Telephony.Carriers.DEFAULT_SORT_ORDER)); + if (cursor != null) { + while (cursor.moveToNext()) { + apnSetting = ApnSetting.makeApnSetting(cursor); + if (apnSetting != null) { + break; + } + } + cursor.close(); + } + return apnSetting; + } + @Override public List<ApnSetting> getOverrideApns(@NonNull ComponentName who) { if (!mHasFeature || !mHasTelephonyFeature) { @@ -18363,54 +18365,38 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void updateNetworkPreferenceForUser(int userId, - boolean preferentialNetworkServiceEnabled) { + List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) { if (!isManagedProfile(userId)) { return; } - ProfileNetworkPreference.Builder preferenceBuilder = - new ProfileNetworkPreference.Builder(); - if (preferentialNetworkServiceEnabled) { - preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); - preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1); - } else { - preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT); - } List<ProfileNetworkPreference> preferences = new ArrayList<>(); - preferences.add(preferenceBuilder.build()); - mInjector.binderWithCleanCallingIdentity(() -> - mInjector.getConnectivityManager().setProfileNetworkPreferences( - UserHandle.of(userId), preferences, - null /* executor */, null /* listener */)); - } - - private void updateNetworkPreferenceForUser(int userId, - PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) { - if (!isManagedProfile(userId)) { - return; - } - ProfileNetworkPreference.Builder preferenceBuilder = - new ProfileNetworkPreference.Builder(); - if (preferentialNetworkServiceConfig.isEnabled()) { - if (preferentialNetworkServiceConfig.isFallbackToDefaultConnectionAllowed()) { - preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + for (PreferentialNetworkServiceConfig preferentialNetworkServiceConfig : + preferentialNetworkServiceConfigs) { + ProfileNetworkPreference.Builder preferenceBuilder = + new ProfileNetworkPreference.Builder(); + if (preferentialNetworkServiceConfig.isEnabled()) { + if (preferentialNetworkServiceConfig.isFallbackToDefaultConnectionAllowed()) { + preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + } else { + preferenceBuilder.setPreference( + PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK); + } } else { - preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK); + preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT); } - } else { - preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT); - } - List<Integer> allowedUids = Arrays.stream( - preferentialNetworkServiceConfig.getIncludedUids()).boxed().collect( - Collectors.toList()); - List<Integer> excludedUids = Arrays.stream( - preferentialNetworkServiceConfig.getExcludedUids()).boxed().collect( - Collectors.toList()); - preferenceBuilder.setIncludedUids(allowedUids); - preferenceBuilder.setExcludedUids(excludedUids); - preferenceBuilder.setPreferenceEnterpriseId( - preferentialNetworkServiceConfig.getNetworkId()); - List<ProfileNetworkPreference> preferences = new ArrayList<>(); - preferences.add(preferenceBuilder.build()); + List<Integer> allowedUids = Arrays.stream( + preferentialNetworkServiceConfig.getIncludedUids()).boxed().collect( + Collectors.toList()); + List<Integer> excludedUids = Arrays.stream( + preferentialNetworkServiceConfig.getExcludedUids()).boxed().collect( + Collectors.toList()); + preferenceBuilder.setIncludedUids(allowedUids); + preferenceBuilder.setExcludedUids(excludedUids); + preferenceBuilder.setPreferenceEnterpriseId( + preferentialNetworkServiceConfig.getNetworkId()); + + preferences.add(preferenceBuilder.build()); + } mInjector.binderWithCleanCallingIdentity(() -> mInjector.getConnectivityManager().setProfileNetworkPreferences( UserHandle.of(userId), preferences, diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7005d534eee8..c8eaa23ed11d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -41,6 +41,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.IPackageManager; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -1227,12 +1228,15 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(domainVerificationService); t.traceEnd(); + IPackageManager iPackageManager; t.traceBegin("StartPackageManagerService"); try { Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain"); - mPackageManagerService = PackageManagerService.main(mSystemContext, installer, - domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, - mOnlyCore); + Pair<PackageManagerService, IPackageManager> pmsPair = PackageManagerService.main( + mSystemContext, installer, domainVerificationService, + mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); + mPackageManagerService = pmsPair.first; + iPackageManager = pmsPair.second; } finally { Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain"); } @@ -1240,7 +1244,7 @@ public final class SystemServer implements Dumpable { // Now that the package manager has started, register the dex load reporter to capture any // dex files loaded by system server. // These dex files will be optimized by the BackgroundDexOptService. - SystemServerDexLoadReporter.configureSystemServerDexReporter(mPackageManagerService); + SystemServerDexLoadReporter.configureSystemServerDexReporter(iPackageManager); mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index c5f990d52b82..66e840b5120b 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -112,7 +112,7 @@ public final class ProfcollectForwardingService extends SystemService { try { mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback); } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage()); } } @@ -123,7 +123,7 @@ public final class ProfcollectForwardingService extends SystemService { try { return !mIProfcollect.get_supported_provider().isEmpty(); } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage()); return false; } } @@ -219,7 +219,8 @@ public final class ProfcollectForwardingService extends SystemService { try { sSelfService.mIProfcollect.process(); } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "Failed to process profiles in background: " + + e.getMessage()); } }); return true; @@ -234,8 +235,11 @@ public final class ProfcollectForwardingService extends SystemService { // Event observers private void registerObservers() { - registerAppLaunchObserver(); - registerOTAObserver(); + BackgroundThread.get().getThreadHandler().post( + () -> { + registerAppLaunchObserver(); + registerOTAObserver(); + }); } private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); @@ -264,7 +268,7 @@ public final class ProfcollectForwardingService extends SystemService { try { mIProfcollect.trace_once("applaunch"); } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage()); } }); } @@ -348,7 +352,7 @@ public final class ProfcollectForwardingService extends SystemService { .putExtra("filename", reportName); context.sendBroadcast(intent); } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "Failed to upload report: " + e.getMessage()); } }); } diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index dcc461be0015..7017440a86bb 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -221,11 +221,14 @@ class PackageManagerComponentLabelIconOverrideTest { @After fun verifyExpectedResult() { assertServiceInitialized() ?: return - if (params.componentName != null) { - val activityInfo = service.getActivityInfo(params.componentName, 0, userId) - if (activityInfo != null) { - assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel) - assertThat(activityInfo.icon).isEqualTo(params.expectedIcon) + if (params.componentName != null && params.result !is Result.Exception) { + // Suppress so that failures in @After don't override the actual test failure + @Suppress("UNNECESSARY_SAFE_CALL") + service?.let { + val activityInfo = it.snapshotComputer() + .getActivityInfo(params.componentName, 0, userId) + assertThat(activityInfo?.nonLocalizedLabel).isEqualTo(params.expectedLabel) + assertThat(activityInfo?.icon).isEqualTo(params.expectedIcon) } } } @@ -237,9 +240,12 @@ class PackageManagerComponentLabelIconOverrideTest { Result.Changed, Result.ChangedWithoutNotify -> { // Suppress so that failures in @After don't override the actual test failure @Suppress("UNNECESSARY_SAFE_CALL") - val activityInfo = service?.getActivityInfo(params.componentName, 0, userIdDifferent) - assertThat(activityInfo?.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL) - assertThat(activityInfo?.icon).isEqualTo(DEFAULT_ICON) + service?.let { + val activityInfo = it.snapshotComputer() + ?.getActivityInfo(params.componentName, 0, userIdDifferent) + assertThat(activityInfo?.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL) + assertThat(activityInfo?.icon).isEqualTo(DEFAULT_ICON) + } } Result.NotChanged, is Result.Exception -> {} }.run { /*exhaust*/ } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index 83ccabf03935..8f81e930d0cd 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -505,11 +505,6 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag ) } ), - getSetByValue( - AndroidPackage::shouldInheritKeyStoreKeys, - ParsingPackage::setInheritKeyStoreKeys, - true - ), getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")), getSetByValue( AndroidPackage::isOnBackInvokedCallbackEnabled, diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java index e89c812ba1fb..4fe9cd30e4ff 100644 --- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java +++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java @@ -28,7 +28,6 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import android.graphics.Bitmap; import android.platform.test.annotations.Presubmit; import android.service.games.GameSession.ScreenshotCallback; import android.testing.AndroidTestingRunner; @@ -61,7 +60,6 @@ import java.util.concurrent.TimeUnit; @TestableLooper.RunWithLooper(setAsMainLooper = true) public final class GameSessionTest { private static final long WAIT_FOR_CALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); - private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); @Mock private IGameSessionController mMockGameSessionController; @@ -101,7 +99,7 @@ public final class GameSessionTest { } @Override - public void onSuccess(Bitmap bitmap) { + public void onSuccess() { fail(); } }); @@ -131,7 +129,7 @@ public final class GameSessionTest { } @Override - public void onSuccess(Bitmap bitmap) { + public void onSuccess() { fail(); } }); @@ -160,7 +158,7 @@ public final class GameSessionTest { } @Override - public void onSuccess(Bitmap bitmap) { + public void onSuccess() { fail(); } }); @@ -170,10 +168,10 @@ public final class GameSessionTest { } @Test - public void takeScreenshot_gameManagerSuccess_returnsBitmap() throws Exception { + public void takeScreenshot_gameManagerSuccess() throws Exception { doAnswer(invocation -> { AndroidFuture result = invocation.getArgument(1); - result.complete(GameScreenshotResult.createSuccessResult(TEST_BITMAP)); + result.complete(GameScreenshotResult.createSuccessResult()); return null; }).when(mMockGameSessionController).takeScreenshot(anyInt(), any()); @@ -187,8 +185,7 @@ public final class GameSessionTest { } @Override - public void onSuccess(Bitmap bitmap) { - assertEquals(TEST_BITMAP, bitmap); + public void onSuccess() { countDownLatch.countDown(); } }); diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index 32a31d0e57f7..319a769bb1de 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -19,6 +19,7 @@ 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.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState; @@ -26,19 +27,21 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verifyZeroInteractions; import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; +import android.app.IProcessObserver; import android.app.ITaskStackListener; import android.content.ComponentName; import android.content.Context; @@ -46,7 +49,12 @@ import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Picture; import android.graphics.Rect; +import android.net.Uri; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; @@ -71,6 +79,7 @@ import com.android.internal.infra.AndroidFuture; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import com.android.internal.util.Preconditions; +import com.android.internal.util.ScreenshotHelper; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; import com.android.server.wm.WindowManagerService; @@ -87,6 +96,7 @@ import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.HashMap; import java.util.Objects; +import java.util.function.Consumer; /** @@ -114,11 +124,22 @@ public final class GameServiceProviderInstanceImplTest { new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity"); - private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); + private static final Bitmap TEST_BITMAP; + static { + Picture picture = new Picture(); + Canvas canvas = picture.beginRecording(200, 100); + Paint p = new Paint(); + p.setColor(Color.BLACK); + canvas.drawCircle(10, 10, 10, p); + picture.endRecording(); + TEST_BITMAP = Bitmap.createBitmap(picture); + } private MockitoSession mMockingSession; private GameServiceProviderInstance mGameServiceProviderInstance; @Mock + private ActivityManagerInternal mMockActivityManagerInternal; + @Mock private IActivityTaskManager mMockActivityTaskManager; @Mock private WindowManagerService mMockWindowManagerService; @@ -126,6 +147,8 @@ public final class GameServiceProviderInstanceImplTest { private WindowManagerInternal mMockWindowManagerInternal; @Mock private IActivityManager mMockActivityManager; + @Mock + private ScreenshotHelper mMockScreenshotHelper; private MockContext mMockContext; private FakeGameClassifier mFakeGameClassifier; private FakeGameService mFakeGameService; @@ -133,6 +156,7 @@ public final class GameServiceProviderInstanceImplTest { private FakeGameSessionService mFakeGameSessionService; private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector; private ArrayList<ITaskStackListener> mTaskStackListeners; + private ArrayList<IProcessObserver> mProcessObservers; private ArrayList<TaskSystemBarsListener> mTaskSystemBarsListeners; private ArrayList<RunningTaskInfo> mRunningTaskInfos; @@ -167,6 +191,16 @@ public final class GameServiceProviderInstanceImplTest { return null; }).when(mMockActivityTaskManager).unregisterTaskStackListener(any()); + mProcessObservers = new ArrayList<>(); + doAnswer(invocation -> { + mProcessObservers.add(invocation.getArgument(0)); + return null; + }).when(mMockActivityManager).registerProcessObserver(any()); + doAnswer(invocation -> { + mProcessObservers.remove(invocation.getArgument(0)); + return null; + }).when(mMockActivityManager).unregisterProcessObserver(any()); + mTaskSystemBarsListeners = new ArrayList<>(); doAnswer(invocation -> { mTaskSystemBarsListeners.add(invocation.getArgument(0)); @@ -188,11 +222,13 @@ public final class GameServiceProviderInstanceImplTest { mMockContext, mFakeGameClassifier, mMockActivityManager, + mMockActivityManagerInternal, mMockActivityTaskManager, mMockWindowManagerService, mMockWindowManagerInternal, mFakeGameServiceConnector, - mFakeGameSessionServiceConnector); + mFakeGameSessionServiceConnector, + mMockScreenshotHelper); } @After @@ -410,6 +446,214 @@ public final class GameServiceProviderInstanceImplTest { } @Test + public void gameProcessStopped_soleProcess_destroysGameSession() throws Exception { + int gameProcessId = 1000; + + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + startProcessForPackage(gameProcessId, GAME_A_PACKAGE); + + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isFalse(); + + // Death of the sole game process destroys the game session. + dispatchProcessDied(gameProcessId); + assertThat(gameSession10.mIsDestroyed).isTrue(); + } + + @Test + public void gameProcessStopped_soleProcess_destroysMultipleGameSessionsForSamePackage() + throws Exception { + int gameProcessId = 1000; + + mGameServiceProviderInstance.start(); + + // Multiple tasks exist for the same package. + startTask(10, GAME_A_MAIN_ACTIVITY); + startTask(11, GAME_A_MAIN_ACTIVITY); + startProcessForPackage(gameProcessId, GAME_A_PACKAGE); + + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); + + assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(gameSession11.mIsDestroyed).isFalse(); + + // Death of the sole game process destroys both game sessions. + dispatchProcessDied(gameProcessId); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(gameSession11.mIsDestroyed).isTrue(); + } + + @Test + public void gameProcessStopped_multipleProcesses_gameSessionDestroyedWhenAllDead() + throws Exception { + int firstGameProcessId = 1000; + int secondGameProcessId = 1001; + + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE); + startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE); + + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isFalse(); + + // Death of the first process (with the second one still alive) does not destroy the game + // session. + dispatchProcessDied(firstGameProcessId); + assertThat(gameSession10.mIsDestroyed).isFalse(); + + // Death of the second process does destroy the game session. + dispatchProcessDied(secondGameProcessId); + assertThat(gameSession10.mIsDestroyed).isTrue(); + } + + @Test + public void gameProcessCreatedAfterInitialProcessDead_newGameSessionCreated() throws Exception { + int firstGameProcessId = 1000; + int secondGameProcessId = 1000; + + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE); + + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + assertThat(gameSession10.mIsDestroyed).isFalse(); + + // After the first game process dies, the game session should be destroyed. + dispatchProcessDied(firstGameProcessId); + assertThat(gameSession10.mIsDestroyed).isTrue(); + + // However, when a new process for the game starts, a new game session should be created. + startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE); + // Verify that a new pending game session is created for the game's taskId. + assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10)); + } + + @Test + public void gameProcessCreatedAfterInitialProcessDead_multipleGameSessionsCreatedSamePackage() + throws Exception { + int firstGameProcessId = 1000; + int secondGameProcessId = 1000; + + mGameServiceProviderInstance.start(); + + // Multiple tasks exist for the same package. + startTask(10, GAME_A_MAIN_ACTIVITY); + startTask(11, GAME_A_MAIN_ACTIVITY); + startProcessForPackage(firstGameProcessId, GAME_A_PACKAGE); + + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + + mFakeGameService.requestCreateGameSession(10); + mFakeGameService.requestCreateGameSession(11); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); + + assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(gameSession11.mIsDestroyed).isFalse(); + + // After the first game process dies, both game sessions for the package should be + // destroyed. + dispatchProcessDied(firstGameProcessId); + assertThat(gameSession10.mIsDestroyed).isTrue(); + assertThat(gameSession11.mIsDestroyed).isTrue(); + + // However, when a new process for the game starts, new game sessions for the same + // package should be created. + startProcessForPackage(secondGameProcessId, GAME_A_PACKAGE); + // Verify that new pending game sessions were created for each of the game's taskIds. + assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(10)); + assertNotNull(mFakeGameSessionService.removePendingFutureForTaskId(11)); + } + + @Test + public void gameProcessStarted_gameSessionNotRequested_doesNothing() throws Exception { + int gameProcessId = 1000; + + mGameServiceProviderInstance.start(); + + // A game task and process are started, but requestCreateGameSession is never called. + startTask(10, GAME_A_MAIN_ACTIVITY); + startProcessForPackage(gameProcessId, GAME_A_PACKAGE); + + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + + // No game session should be created. + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); + } + + @Test + public void processActivityAndDeath_notForGame_gameSessionUnaffected() throws Exception { + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + // Process activity for a process without a known package is ignored. + startProcessForPackage(1000, /*packageName=*/ null); + dispatchProcessActivity(1000); + dispatchProcessDied(1000); + + // Process activity for a process with a different package is ignored + startProcessForPackage(1001, GAME_B_PACKAGE); + dispatchProcessActivity(1001); + dispatchProcessDied(1001); + + // Death of a process for which there was no activity is ignored + dispatchProcessDied(1002); + + // Despite all the process activity and death, the game session is not destroyed. + assertThat(gameSession10.mIsDestroyed).isFalse(); + } + + @Test public void taskSystemBarsListenerChanged_noAssociatedGameSession_doesNothing() { mGameServiceProviderInstance.start(); @@ -425,6 +669,7 @@ public final class GameServiceProviderInstanceImplTest { public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -446,6 +691,7 @@ public final class GameServiceProviderInstanceImplTest { public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -799,27 +1045,32 @@ public final class GameServiceProviderInstanceImplTest { SurfaceControl mockOverlaySurfaceControl = Mockito.mock(SurfaceControl.class); SurfaceControl[] excludeLayers = new SurfaceControl[1]; excludeLayers[0] = mockOverlaySurfaceControl; + int taskId = 10; when(mMockWindowManagerService.captureTaskBitmap(eq(10), any())).thenReturn(TEST_BITMAP); - + doAnswer(invocation -> { + Consumer<Uri> consumer = invocation.getArgument(invocation.getArguments().length - 1); + consumer.accept(Uri.parse("a/b.png")); + return null; + }).when(mMockScreenshotHelper).provideScreenshot( + any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any()); mGameServiceProviderInstance.start(); - startTask(10, GAME_A_MAIN_ACTIVITY); + startTask(taskId, GAME_A_MAIN_ACTIVITY); mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); - mFakeGameService.requestCreateGameSession(10); + mFakeGameService.requestCreateGameSession(taskId); FakeGameSession gameSession10 = new FakeGameSession(); SurfacePackage mockOverlaySurfacePackage = Mockito.mock(SurfacePackage.class); when(mockOverlaySurfacePackage.getSurfaceControl()).thenReturn(mockOverlaySurfaceControl); - mFakeGameSessionService.removePendingFutureForTaskId(10) + mFakeGameSessionService.removePendingFutureForTaskId(taskId) .complete(new CreateGameSessionResult(gameSession10, mockOverlaySurfacePackage)); IGameSessionController gameSessionController = getOnlyElement( mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController; AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>(); - gameSessionController.takeScreenshot(10, resultFuture); + gameSessionController.takeScreenshot(taskId, resultFuture); GameScreenshotResult result = resultFuture.get(); assertEquals(GameScreenshotResult.GAME_SCREENSHOT_SUCCESS, result.getStatus()); - assertEquals(TEST_BITMAP, result.getBitmap()); } @Test @@ -874,7 +1125,8 @@ public final class GameServiceProviderInstanceImplTest { mFakeGameSessionService.getCapturedCreateInvocations()) .mGameSessionController.restartGame(11); - verifyZeroInteractions(mMockActivityManager); + verify(mMockActivityManager).registerProcessObserver(any()); + verifyNoMoreInteractions(mMockActivityManager); assertThat(mMockContext.getLastStartedIntent()).isNull(); } @@ -906,7 +1158,6 @@ public final class GameServiceProviderInstanceImplTest { dispatchTaskRemoved(taskId); } - private void dispatchTaskRemoved(int taskId) { dispatchTaskChangeEvent(taskStackListener -> { taskStackListener.onTaskRemoved(taskId); @@ -932,6 +1183,37 @@ public final class GameServiceProviderInstanceImplTest { } } + private void startProcessForPackage(int processId, @Nullable String packageName) { + if (packageName != null) { + when(mMockActivityManagerInternal.getPackageNameByPid(processId)).thenReturn( + packageName); + } + + dispatchProcessActivity(processId); + } + + private void dispatchProcessActivity(int processId) { + dispatchProcessChangedEvent(processObserver -> { + // Neither uid nor foregroundActivities are used by the implementation being tested. + processObserver.onForegroundActivitiesChanged(processId, /*uid=*/ + 0, /*foregroundActivities=*/ false); + }); + } + + private void dispatchProcessDied(int processId) { + dispatchProcessChangedEvent(processObserver -> { + // The uid param is not used by the implementation being tested. + processObserver.onProcessDied(processId, /*uid=*/ 0); + }); + } + + private void dispatchProcessChangedEvent( + ThrowingConsumer<IProcessObserver> processObserverConsumer) { + for (IProcessObserver processObserver : mProcessObservers) { + processObserverConsumer.accept(processObserver); + } + } + private void mockPermissionGranted(String permission) { mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED); } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index b5ad459d29f7..784f732ba3b1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -754,6 +754,57 @@ public class LocalDisplayAdapterTest { verify(mMockedBacklight, never()).setBrightness(anyFloat()); } + @Test + public void testGetSystemPreferredDisplayMode() throws Exception { + SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f); + // preferred mode + SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f); + + SurfaceControl.DisplayMode[] modes = + new SurfaceControl.DisplayMode[]{displayMode1, displayMode2}; + FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays).isEmpty(); + + DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( + 0).getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); + + Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(matches(defaultMode, displayMode2)).isTrue(); + + // Change the display and add new preferred mode + SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(2, 2340, 1080, 60f); + modes = new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, addedDisplayInfo}; + display.dynamicInfo.supportedDisplayModes = modes; + display.dynamicInfo.preferredBootDisplayMode = 2; + setUpDisplay(display); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertTrue(mListener.traversalRequested); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays.size()).isEqualTo(1); + + DisplayDevice displayDevice = mListener.changedDisplays.get(0); + displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); + displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2); + assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo); + + assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), addedDisplayInfo)) + .isTrue(); + } + private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort, float expectedXdpi, float expectedYDpi, @@ -831,6 +882,16 @@ public class LocalDisplayAdapterTest { dynamicInfo.supportedDisplayModes = modes; dynamicInfo.activeDisplayModeId = activeMode; } + + private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode, + int preferredMode) { + address = createDisplayAddress(port); + info = createFakeDisplayInfo(); + dynamicInfo.supportedDisplayModes = modes; + dynamicInfo.activeDisplayModeId = activeMode; + dynamicInfo.preferredBootDisplayMode = preferredMode; + } + } private void setUpDisplay(FakeDisplay display) { @@ -843,6 +904,7 @@ public class LocalDisplayAdapterTest { .thenReturn(display.dynamicInfo); when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token)) .thenReturn(display.desiredDisplayModeSpecs); + when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true); } private void updateAvailableDisplays() { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt index ccfeb4c9df51..fbbb814388f7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt @@ -74,6 +74,10 @@ class PackageFreezerTest { assertThat(assertFailsWith(exceptionClass, block).message).contains(message) } + private fun checkPackageStartable() { + pms.checkPackageStartable(pms.snapshotComputer(), TEST_PACKAGE, TEST_USER_ID) + } + @Before @Throws(Exception::class) fun setup() { @@ -89,11 +93,11 @@ class PackageFreezerTest { .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) + checkPackageStartable() } freezer.close() - pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + checkPackageStartable() } @Test @@ -104,16 +108,16 @@ class PackageFreezerTest { .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) + checkPackageStartable() } freezer1.close() assertThrowContainsMessage(SecurityException::class, frozenMessage(TEST_PACKAGE)) { - pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + checkPackageStartable() } freezer2.close() - pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + checkPackageStartable() } @Test @@ -123,13 +127,13 @@ class PackageFreezerTest { .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) + checkPackageStartable() } freezer = null System.gc() System.runFinalization() - pms.checkPackageStartable(TEST_PACKAGE, TEST_USER_ID) + checkPackageStartable() } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt index a6c7bfb456be..13199032a223 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt @@ -107,7 +107,7 @@ class PackageManagerServiceHibernationTests { whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID)) .thenReturn(true) - pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) + pm.setPackageStoppedState(pm.snapshotComputer(), TEST_PACKAGE_NAME, false, TEST_USER_ID) TestableLooper.get(this).processAllMessages() @@ -133,7 +133,8 @@ class PackageManagerServiceHibernationTests { .thenReturn(true) try { - pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID) + pm.setPackageStoppedState(pm.snapshotComputer(), TEST_PACKAGE_NAME, false, + TEST_USER_ID) TestableLooper.get(this).processAllMessages() } catch (e: Exception) { Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e") diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index f7b1dd5219d6..1464405cca08 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -39,8 +39,7 @@ import android.content.Context; import android.content.pm.ApexStagedEvent; import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionInfo.SessionErrorCode; +import android.content.pm.PackageManager; import android.content.pm.StagedApexInfo; import android.os.SystemProperties; import android.os.storage.IStorageManager; @@ -158,10 +157,10 @@ public class StagingManagerTest { mStagingManager.restoreSessions(Arrays.asList(session1, session2), true); - assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + assertThat(session1.getErrorCode()).isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed"); - assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + assertThat(session2.getErrorCode()).isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed"); } @@ -247,12 +246,12 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a " + "staged session supposed to be activated"); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -303,22 +302,22 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession1.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. " + "Error: Failed for test"); assertThat(apexSession2.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't " + "activate nor fail. Marking it as failed anyway."); assertThat(apexSession3.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a " + "staged session supposed to be activated"); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -351,12 +350,12 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't " + "activate nor fail. Marking it as failed anyway."); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -445,11 +444,11 @@ public class StagingManagerTest { verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false)); assertThat(apexSession.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state"); assertThat(apkSession.getErrorCode()) - .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED); + .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED); assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed"); } @@ -755,7 +754,7 @@ public class StagingManagerTest { /* isReady */ false, /* isFailed */ false, /* isApplied */false, - /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.SESSION_NO_ERROR, + /* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN, /* stagedSessionErrorMessage */ "no error"); StagingManager.StagedSession stagedSession = spy(session.mStagedSession); @@ -775,7 +774,7 @@ public class StagingManagerTest { private boolean mIsReady = false; private boolean mIsApplied = false; private boolean mIsFailed = false; - private @SessionErrorCode int mErrorCode = -1; + private int mErrorCode = -1; private String mErrorMessage; private boolean mIsDestroyed = false; private int mParentSessionId = -1; @@ -828,7 +827,7 @@ public class StagingManagerTest { return this; } - private @SessionErrorCode int getErrorCode() { + private int getErrorCode() { return mErrorCode; } @@ -940,7 +939,7 @@ public class StagingManagerTest { } @Override - public void setSessionFailed(@SessionErrorCode int errorCode, String errorMessage) { + public void setSessionFailed(int errorCode, String errorMessage) { Preconditions.checkState(!mIsApplied, "Already marked as applied"); mIsFailed = true; mErrorCode = errorCode; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index b601d14f1f58..1ebcbe10fae6 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -4157,8 +4157,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testSetPreferentialNetworkServiceConfig_noProfileOwner() throws Exception { assertExpectException(SecurityException.class, null, - () -> dpm.setPreferentialNetworkServiceConfig( - PreferentialNetworkServiceConfig.DEFAULT)); + () -> dpm.setPreferentialNetworkServiceConfigs( + List.of(PreferentialNetworkServiceConfig.DEFAULT))); } @Test @@ -4198,10 +4198,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { addManagedProfile(admin1, managedProfileAdminUid, admin1); mContext.binder.callingUid = managedProfileAdminUid; - dpm.setPreferentialNetworkServiceConfig(PreferentialNetworkServiceConfig.DEFAULT); + dpm.setPreferentialNetworkServiceConfigs( + List.of(PreferentialNetworkServiceConfig.DEFAULT)); assertThat(dpm.isPreferentialNetworkServiceEnabled()).isFalse(); - assertThat(dpm.getPreferentialNetworkServiceConfig() - .isEnabled()).isFalse(); + assertThat(dpm.getPreferentialNetworkServiceConfigs().get(0).isEnabled()).isFalse(); ProfileNetworkPreference preferenceDetails = new ProfileNetworkPreference.Builder() @@ -4227,8 +4227,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { addManagedProfile(admin1, managedProfileAdminUid, admin1); mContext.binder.callingUid = managedProfileAdminUid; - dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled); - assertThat(dpm.getPreferentialNetworkServiceConfig() + dpm.setPreferentialNetworkServiceConfigs(List.of(preferentialNetworkServiceConfigEnabled)); + assertThat(dpm.getPreferentialNetworkServiceConfigs().get(0) .isEnabled()).isTrue(); ProfileNetworkPreference preferenceDetails = new ProfileNetworkPreference.Builder() @@ -4257,8 +4257,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { .setFallbackToDefaultConnectionAllowed(false) .setIncludedUids(new int[]{1, 2}) .build(); - dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled); - assertThat(dpm.getPreferentialNetworkServiceConfig() + dpm.setPreferentialNetworkServiceConfigs(List.of(preferentialNetworkServiceConfigEnabled)); + assertThat(dpm.getPreferentialNetworkServiceConfigs().get(0) .isEnabled()).isTrue(); List<Integer> includedList = new ArrayList<>(); includedList.add(1); @@ -4292,8 +4292,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { .setExcludedUids(new int[]{1, 2}) .build(); - dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled); - assertThat(dpm.getPreferentialNetworkServiceConfig() + dpm.setPreferentialNetworkServiceConfigs(List.of(preferentialNetworkServiceConfigEnabled)); + assertThat(dpm.getPreferentialNetworkServiceConfigs().get(0) .isEnabled()).isTrue(); List<Integer> excludedUids = new ArrayList<>(); excludedUids.add(1); diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java index 5185e15a8557..db602ca83f30 100644 --- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -98,11 +98,8 @@ public class SystemAppUpdateTrackerTest { private ActivityTaskManagerInternal mMockActivityTaskManager; @Mock private ActivityManagerInternal mMockActivityManager; - @Mock - private LocaleManagerBackupHelper mMockLocaleManagerBackupHelper; - @Mock - PackageMonitor mMockPackageMonitor; + PackageMonitor mPackageMonitor; private LocaleManagerService mLocaleManagerService; // Object under test. @@ -114,11 +111,14 @@ public class SystemAppUpdateTrackerTest { mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class); mMockActivityManager = mock(ActivityManagerInternal.class); mMockPackageManagerInternal = mock(PackageManagerInternal.class); - mMockPackageMonitor = mock(PackageMonitor.class); - mMockLocaleManagerBackupHelper = mock(ShadowLocaleManagerBackupHelper.class); + LocaleManagerBackupHelper mockLocaleManagerBackupHelper = + mock(ShadowLocaleManagerBackupHelper.class); + // PackageMonitor is not needed in LocaleManagerService for these tests hence it is + // passed as null. mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager, mMockActivityManager, - mMockPackageManagerInternal, mMockLocaleManagerBackupHelper, mMockPackageMonitor); + mMockPackageManagerInternal, mockLocaleManagerBackupHelper, + /* mPackageMonitor= */ null); doReturn(DEFAULT_USER_ID).when(mMockActivityManager) .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(), @@ -134,6 +134,9 @@ public class SystemAppUpdateTrackerTest { mSystemAppUpdateTracker = new SystemAppUpdateTracker(mMockContext, mLocaleManagerService, mStoragefile); + + mPackageMonitor = new LocaleManagerServicePackageMonitor( + mockLocaleManagerBackupHelper, mSystemAppUpdateTracker); } @After @@ -148,7 +151,7 @@ public class SystemAppUpdateTrackerTest { .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any()); // Updates the app once so that it writes to the file. - mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, Binder.getCallingUid()); // Clear the in-memory data of updated apps mSystemAppUpdateTracker.getUpdatedApps().clear(); @@ -167,7 +170,7 @@ public class SystemAppUpdateTrackerTest { DEFAULT_LOCALES)).when(mMockActivityTaskManager) .getApplicationConfig(anyString(), anyInt()); - mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, Binder.getCallingUid()); assertBroadcastSentToInstaller(DEFAULT_PACKAGE_NAME_1, DEFAULT_LOCALES); @@ -186,7 +189,7 @@ public class SystemAppUpdateTrackerTest { .getApplicationConfig(anyString(), anyInt()); // first update - mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, Binder.getCallingUid()); assertBroadcastSentToInstaller(DEFAULT_PACKAGE_NAME_1, DEFAULT_LOCALES); @@ -195,7 +198,7 @@ public class SystemAppUpdateTrackerTest { verifyStorageFileContents(expectedAppList); // second update - mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, Binder.getCallingUid()); // getApplicationLocales should be invoked only once on the first update. verify(mMockActivityTaskManager, times(1)) @@ -212,7 +215,7 @@ public class SystemAppUpdateTrackerTest { /* isUpdatedSystemApp = */false)) .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_2), any()); - mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_2, + mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_2, Binder.getCallingUid()); assertTrue(!mSystemAppUpdateTracker.getUpdatedApps().contains(DEFAULT_PACKAGE_NAME_2)); @@ -231,7 +234,7 @@ public class SystemAppUpdateTrackerTest { .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any()); doReturn(null).when(mMockPackageManager).getInstallSourceInfo(anyString()); - mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, Binder.getCallingUid()); // getApplicationLocales should be never be invoked if not installer is not present. diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index 3d21b74825f0..27c3ca46cb20 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -20,10 +20,15 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; import android.util.AtomicFile; import android.util.Slog; @@ -68,12 +73,17 @@ public class PackageInstallerSessionTest { @Mock PackageManagerService mMockPackageManagerInternal; + @Mock + Computer mSnapshot; + @Before public void setUp() throws Exception { mTmpDir = mTemporaryFolder.newFolder("PackageInstallerSessionTest"); mSessionsFile = new AtomicFile( new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session"); MockitoAnnotations.initMocks(this); + when(mSnapshot.getPackageUid(anyString(), anyLong(), anyInt())).thenReturn(0); + when(mMockPackageManagerInternal.snapshotComputer()).thenReturn(mSnapshot); } @Test @@ -188,7 +198,7 @@ public class PackageInstallerSessionTest { /* isFailed */ false, /* isApplied */false, /* stagedSessionErrorCode */ - PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED, + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, /* stagedSessionErrorMessage */ "some error"); } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 2b34bc2ef28d..f4ab3db3c917 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -31,11 +31,11 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -1103,6 +1103,59 @@ public class PackageManagerSettingsTests { assertThat(countDownLatch.getCount(), is(0L)); } + @Test + public void testRegisterAndRemoveAppId() throws PackageManagerException { + // Test that the first new app UID should start from FIRST_APPLICATION_UID + final Settings settings = makeSettings(); + final PackageSetting ps = createPackageSetting("com.foo"); + assertTrue(settings.registerAppIdLPw(ps, false)); + assertEquals(10000, ps.getAppId()); + // Set up existing app IDs: 10000, 10001, 10003 + final PackageSetting ps1 = createPackageSetting("com.foo1"); + ps1.setAppId(10001); + final PackageSetting ps2 = createPackageSetting("com.foo2"); + ps2.setAppId(10003); + final PackageSetting ps3 = createPackageSetting("com.foo3"); + assertEquals(0, ps3.getAppId()); + assertTrue(settings.registerAppIdLPw(ps1, false)); + assertTrue(settings.registerAppIdLPw(ps2, false)); + assertTrue(settings.registerAppIdLPw(ps3, false)); + assertEquals(10001, ps1.getAppId()); + assertEquals(10003, ps2.getAppId()); + // Expecting the new one to start with the next available uid + assertEquals(10002, ps3.getAppId()); + // Remove and insert a new one and the new one should not reuse the same uid + settings.removeAppIdLPw(10002); + final PackageSetting ps4 = createPackageSetting("com.foo4"); + assertTrue(settings.registerAppIdLPw(ps4, false)); + assertEquals(10004, ps4.getAppId()); + // Keep adding more + final PackageSetting ps5 = createPackageSetting("com.foo5"); + assertTrue(settings.registerAppIdLPw(ps5, false)); + assertEquals(10005, ps5.getAppId()); + // Remove the last one and the new one should use incremented uid + settings.removeAppIdLPw(10005); + final PackageSetting ps6 = createPackageSetting("com.foo6"); + assertTrue(settings.registerAppIdLPw(ps6, false)); + assertEquals(10006, ps6.getAppId()); + } + + /** + * Test replacing a PackageSetting with a SharedUserSetting in mAppIds + */ + @Test + public void testAddPackageSetting() throws PackageManagerException { + final Settings settings = makeSettings(); + final SharedUserSetting sus1 = new SharedUserSetting( + "TestUser", 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/); + sus1.mAppId = 10001; + final PackageSetting ps1 = createPackageSetting("com.foo"); + ps1.setAppId(10001); + assertTrue(settings.registerAppIdLPw(ps1, false)); + settings.addPackageSettingLPw(ps1, sus1); + assertSame(sus1, settings.getSharedUserSettingLPr(ps1)); + } + private void verifyUserState(PackageUserState userState, boolean notLaunched, boolean stopped, boolean installed) { assertThat(userState.getEnabledState(), is(0)); diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 18d3f3d0e805..1ce957d1e768 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -489,6 +489,7 @@ public class AppStandbyControllerTests { @Before public void setUp() throws Exception { + LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); LocalServices.addService( UsageStatsManagerInternal.class, mock(UsageStatsManagerInternal.class)); MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext()); 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 71f8b8de032b..67382bfacab4 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.Notification.FLAG_AUTO_CANCEL; @@ -432,8 +433,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner); when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG}); when(mPackageManagerClient.getPackagesForUid(anyInt())).thenReturn(new String[]{PKG}); - when(mPermissionPolicyInternal.canShowPermissionPromptForTask( - any(ActivityManager.RecentTaskInfo.class))).thenReturn(false); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())) + .thenReturn(INVALID_TASK_ID); mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class)); when(mUm.getProfileIds(0, false)).thenReturn(new int[]{0}); @@ -971,8 +972,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateNotificationChannels_FirstChannelWithFgndTaskStartsPermDialog() throws Exception { - when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any( - ActivityManager.RecentTaskInfo.class))).thenReturn(true); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())).thenReturn(TEST_TASK_ID); final NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); mBinderService.createNotificationChannels(PKG_NO_CHANNELS, @@ -985,8 +985,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateNotificationChannels_SecondChannelWithFgndTaskDoesntStartPermDialog() throws Exception { - when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any( - ActivityManager.RecentTaskInfo.class))).thenReturn(true); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())).thenReturn(TEST_TASK_ID); assertTrue(mBinderService.getNumNotificationChannelsForPackage(PKG, mUid, true) > 0); final NotificationChannel channel = @@ -1001,8 +1000,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCreateNotificationChannels_FirstChannelWithBgndTaskDoesntStartPermDialog() throws Exception { reset(mPermissionPolicyInternal); - when(mPermissionPolicyInternal.canShowPermissionPromptForTask(any( - ActivityManager.RecentTaskInfo.class))).thenReturn(false); + when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())).thenReturn(TEST_TASK_ID); final NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); @@ -2707,13 +2705,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateAppNotifyCreatorBlock() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - - // should not trigger a broadcast - when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED); - mService.mAppOpsCallback.opChanged(0, mUid, PKG); + when(mPreferencesHelper.getImportance(PKG, mUid)).thenReturn(IMPORTANCE_DEFAULT); // should trigger a broadcast - mBinderService.setNotificationsEnabledForPackage(PKG, 0, true); + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); Thread.sleep(500); waitForIdle(); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); @@ -2722,7 +2717,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, captor.getValue().getAction()); assertEquals(PKG, captor.getValue().getPackage()); - assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); } @Test @@ -2739,7 +2734,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // should not trigger a broadcast when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED); - mService.mAppOpsCallback.opChanged(0, mUid, PKG); // should trigger a broadcast mBinderService.setNotificationsEnabledForPackage(PKG, 0, true); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index a83887202a7d..2ba587d21163 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -606,9 +606,9 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { @Test public void testUpdateAppNotifyCreatorBlock() throws Exception { - when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); - mService.mAppOpsCallback.opChanged(0, mUid, PKG); + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false); Thread.sleep(500); waitForIdle(); @@ -618,14 +618,14 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED, captor.getValue().getAction()); assertEquals(PKG, captor.getValue().getPackage()); - assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); + assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)); } @Test public void testUpdateAppNotifyCreatorUnblock() throws Exception { - when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED); + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); - mService.mAppOpsCallback.opChanged(0, mUid, PKG); + mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true); Thread.sleep(500); waitForIdle(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java index a24ba0d1e1c2..50151bfb7191 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java @@ -89,6 +89,10 @@ public class PermissionHelperTest extends UiServiceTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true); + PackageInfo testPkgInfo = new PackageInfo(); + testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS }; + when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt())) + .thenReturn(testPkgInfo); } // TODO (b/194833441): Remove when the migration is enabled @@ -384,6 +388,22 @@ public class PermissionHelperTest extends UiServiceTestCase { } @Test + public void testSetNotificationPermission_doesntRequestNotChanged() throws Exception { + when(mPmi.checkPermission(anyString(), anyString(), anyInt())) + .thenReturn(PERMISSION_GRANTED); + PackageInfo testPkgInfo = new PackageInfo(); + testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.RECORD_AUDIO }; + when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt())) + .thenReturn(testPkgInfo); + mPermissionHelper.setNotificationPermission("pkg", 10, false, false); + + verify(mPmi, never()).checkPermission( + eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10)); + verify(mPermManager, never()).revokeRuntimePermission( + eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString()); + } + + @Test public void testIsPermissionFixed() throws Exception { when(mPermManager.getPermissionFlags(anyString(), eq(Manifest.permission.POST_NOTIFICATIONS), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 8f1eed89647c..7d5a0d0bf84d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -1870,46 +1870,46 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testGetChannelsBypassingDndCount_noChannelsBypassing() throws Exception { assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1, - USER.getIdentifier()).getList().size()); + UID_N_MR1).getList().size()); } @Test - public void testGetChannelsBypassingDnd_noChannelsForUserIdBypassing() + public void testGetChannelsBypassingDnd_noChannelsForUidBypassing() throws Exception { - int user = 9; + int uid = 222; NotificationChannel channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_MAX); channel.setBypassDnd(true); mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true); assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1, - user).getList().size()); + uid).getList().size()); } @Test public void testGetChannelsBypassingDndCount_oneChannelBypassing_groupBlocked() { - int user = USER.getIdentifier(); + int uid = UID_N_MR1; NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1"); NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MAX); channel1.setBypassDnd(true); channel1.setGroup(ncg.getId()); - mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ true); - mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true); + mHelper.createNotificationChannelGroup(PKG_N_MR1, uid, ncg, /* fromTargetApp */ true); + mHelper.createNotificationChannel(PKG_N_MR1, uid, channel1, true, /*has DND access*/ true); assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1, - user).getList().size()); + uid).getList().size()); // disable group ncg.setBlocked(true); - mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ false); + mHelper.createNotificationChannelGroup(PKG_N_MR1, uid, ncg, /* fromTargetApp */ false); assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1, - user).getList().size()); + uid).getList().size()); } @Test public void testGetChannelsBypassingDndCount_multipleChannelsBypassing() { - int user = USER.getIdentifier(); + int uid = UID_N_MR1; NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MAX); NotificationChannel channel2 = new NotificationChannel("id2", "name2", @@ -1920,22 +1920,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel2.setBypassDnd(true); channel3.setBypassDnd(true); // has DND access, so can set bypassDnd attribute - mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true); - mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true); - mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true); + mHelper.createNotificationChannel(PKG_N_MR1, uid, channel1, true, /*has DND access*/ true); + mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true); + mHelper.createNotificationChannel(PKG_N_MR1, uid, channel3, true, true); assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1, - user).getList().size()); + uid).getList().size()); // setBypassDnd false for some channels channel1.setBypassDnd(false); channel2.setBypassDnd(false); assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1, - user).getList().size()); + uid).getList().size()); // setBypassDnd false for rest of the channels channel3.setBypassDnd(false); assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1, - user).getList().size()); + uid).getList().size()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java index abcc8c1e99cb..8ac729e29424 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java @@ -121,7 +121,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { // Create a notification record with the people String array as the // bundled extras, and the numbers ArraySet as additional phone numbers. - private NotificationRecord getRecordWithPeopleInfo(String[] people, + private NotificationRecord getCallRecordWithPeopleInfo(String[] people, ArraySet<String> numbers) { // set up notification record NotificationRecord r = mock(NotificationRecord.class); @@ -131,6 +131,8 @@ public class ZenModeFilteringTest extends UiServiceTestCase { when(sbn.getNotification()).thenReturn(notification); when(r.getSbn()).thenReturn(sbn); when(r.getPhoneNumbers()).thenReturn(numbers); + when(r.getCriticality()).thenReturn(CriticalNotificationExtractor.NORMAL); + when(r.isCategory(CATEGORY_CALL)).thenReturn(true); return r; } @@ -350,11 +352,41 @@ public class ZenModeFilteringTest extends UiServiceTestCase { } @Test + public void testRepeatCallers_checksPhoneNumbers() { + // set up telephony manager behavior + when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); + + // first, record a phone call from a telephone number + String[] callNumber = new String[]{"tel:12345678910"}; + mZenModeFiltering.recordCall(getCallRecordWithPeopleInfo(callNumber, null)); + + // set up policy to only allow repeat callers + Policy policy = new Policy( + PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE); + + // make sure that a record with the phone number in extras is correctly allowed through + NotificationRecord r = getCallRecordWithPeopleInfo(callNumber, null); + assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); + + // make sure that a record with the phone number in the phone numbers array is also + // allowed through + NotificationRecord r2 = getCallRecordWithPeopleInfo(new String[]{"some_contact_uri"}, + new ArraySet<>(new String[]{"12345678910"})); + assertFalse(mZenModeFiltering.shouldIntercept( + ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r2)); + + // A record with the phone number in neither of the above should be intercepted + NotificationRecord r3 = getCallRecordWithPeopleInfo(new String[]{"tel:10987654321"}, + new ArraySet<>(new String[]{"15555555555"})); + assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r3)); + } + + @Test public void testMatchesCallFilter_repeatCallers_directMatch() { // after calls given an email with an exact string match, make sure that // matchesCallFilter returns the right thing String[] mailSource = new String[]{"mailto:hello.world"}; - mZenModeFiltering.recordCall(getRecordWithPeopleInfo(mailSource, null)); + mZenModeFiltering.recordCall(getCallRecordWithPeopleInfo(mailSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -377,7 +409,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); String[] telSource = new String[]{"tel:+1-617-555-1212"}; - mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null)); + mZenModeFiltering.recordCall(getCallRecordWithPeopleInfo(telSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -421,7 +453,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); String[] telSource = new String[]{"tel:%2B16175551212"}; - mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null)); + mZenModeFiltering.recordCall(getCallRecordWithPeopleInfo(telSource, null)); // set up policy to only allow repeat callers Policy policy = new Policy( @@ -468,7 +500,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase { String[] contactSource = new String[]{"content://contacts/lookup/uri-here"}; ArraySet<String> contactNumbers = new ArraySet<>( new String[]{"1-617-555-1212", "1-617-555-3434"}); - NotificationRecord record = getRecordWithPeopleInfo(contactSource, contactNumbers); + NotificationRecord record = getCallRecordWithPeopleInfo(contactSource, contactNumbers); record.mergePhoneNumbers(contactNumbers); mZenModeFiltering.recordCall(record); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index a89c5a1fbf1f..40ab8eb70c04 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2217,6 +2217,19 @@ public class ActivityRecordTests extends WindowTestsBase { assertTrue(activity.pictureInPictureArgs.isLaunchIntoPip()); } + @Test + public void testTransferLaunchCookieWhenFinishing() { + final ActivityRecord activity1 = createActivityWithTask(); + final Binder launchCookie = new Binder(); + activity1.mLaunchCookie = launchCookie; + final ActivityRecord activity2 = createActivityRecord(activity1.getTask()); + activity1.setState(PAUSED, "test"); + activity1.makeFinishingLocked(); + + assertEquals(launchCookie, activity2.mLaunchCookie); + assertNull(activity1.mLaunchCookie); + } + private void verifyProcessInfoUpdate(ActivityRecord activity, State state, boolean shouldUpdate, boolean activityChange) { reset(activity.app); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index 0c8394e75fbc..24ff3f96100a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -41,9 +41,11 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -58,8 +60,6 @@ import com.android.internal.app.SuspendedAppActivity; import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; -import com.android.server.pm.PackageManagerService; -import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult; import org.junit.After; import org.junit.Before; @@ -113,7 +113,7 @@ public class ActivityStartInterceptorTest { @Mock private KeyguardManager mKeyguardManager; @Mock - private PackageManagerService mPackageManager; + private IPackageManager mPackageManager; @Mock private ActivityManagerInternal mAmInternal; @Mock @@ -126,7 +126,7 @@ public class ActivityStartInterceptorTest { new SparseArray<>(); @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mService.mAmInternal = mAmInternal; mInterceptor = new ActivityStartInterceptor( @@ -279,7 +279,7 @@ public class ActivityStartInterceptorTest { } @Test - public void testHarmfulAppWarning() { + public void testHarmfulAppWarning() throws RemoteException { // GIVEN the package we're about to launch has a harmful app warning set when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID)) .thenReturn("This app is bad"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 6fe2d337e4a0..f9aa4b17bc2c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -308,12 +308,12 @@ public class ActivityStarterTests extends WindowTestsBase { } private ActivityStarter prepareStarter(@Intent.Flags int launchFlags) { - return prepareStarter(launchFlags, true /* mockGetLaunchStack */, LAUNCH_MULTIPLE); + return prepareStarter(launchFlags, true /* mockGetRootTask */, LAUNCH_MULTIPLE); } private ActivityStarter prepareStarter(@Intent.Flags int launchFlags, - boolean mockGetLaunchStack) { - return prepareStarter(launchFlags, mockGetLaunchStack, LAUNCH_MULTIPLE); + boolean mockGetRootTask) { + return prepareStarter(launchFlags, mockGetRootTask, LAUNCH_MULTIPLE); } private void setupImeWindow() { @@ -326,20 +326,20 @@ public class ActivityStarterTests extends WindowTestsBase { * Creates a {@link ActivityStarter} with default parameters and necessary mocks. * * @param launchFlags The intent flags to launch activity. - * @param mockGetLaunchStack Whether to mock {@link RootWindowContainer#getLaunchRootTask} for + * @param mockGetRootTask Whether to mock {@link RootWindowContainer#getOrCreateRootTask} for * always launching to the testing stack. Set to false when allowing * the activity can be launched to any stack that is decided by real * implementation. * @return A {@link ActivityStarter} with default setup. */ private ActivityStarter prepareStarter(@Intent.Flags int launchFlags, - boolean mockGetLaunchStack, int launchMode) { + boolean mockGetRootTask, int launchMode) { // always allow test to start activity. doReturn(true).when(mSupervisor).checkStartAnyActivityPermission( any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), any(), any(), any()); - if (mockGetLaunchStack) { + if (mockGetRootTask) { // Instrument the stack and task used. final Task stack = mRootWindowContainer.getDefaultTaskDisplayArea() .createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, @@ -347,9 +347,9 @@ public class ActivityStarterTests extends WindowTestsBase { // Direct starter to use spy stack. doReturn(stack).when(mRootWindowContainer) - .getLaunchRootTask(any(), any(), any(), anyBoolean()); - doReturn(stack).when(mRootWindowContainer).getLaunchRootTask(any(), any(), any(), any(), - anyBoolean(), any(), anyInt()); + .getOrCreateRootTask(any(), any(), any(), anyBoolean()); + doReturn(stack).when(mRootWindowContainer).getOrCreateRootTask(any(), any(), any(), + any(), anyBoolean(), any(), anyInt()); } // Set up mock package manager internal and make sure no unmocked methods are called @@ -434,7 +434,7 @@ public class ActivityStarterTests extends WindowTestsBase { public void testSplitScreenDeliverToTop() { final ActivityStarter starter = prepareStarter( FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, - false /* mockGetLaunchStack */); + false /* mockGetRootTask */); final Pair<ActivityRecord, ActivityRecord> activities = createActivitiesInSplit(); final ActivityRecord splitPrimaryFocusActivity = activities.first; final ActivityRecord splitSecondReusableActivity = activities.second; @@ -790,7 +790,7 @@ public class ActivityStarterTests extends WindowTestsBase { finishingTopActivity.finishing = true; // Launch the bottom task of the target root task. - prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetLaunchStack */) + prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */) .setReason("testBringTaskToFrontWhenFocusedTaskIsFinishing") .setIntent(activity.intent.addFlags( FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) @@ -809,7 +809,7 @@ public class ActivityStarterTests extends WindowTestsBase { @Test public void testDeliverIntentToTopActivityOfNonTopDisplay() { final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, - false /* mockGetLaunchStack */); + false /* mockGetRootTask */); // Create a secondary display at bottom. final TestDisplayContent secondaryDisplay = @@ -849,7 +849,7 @@ public class ActivityStarterTests extends WindowTestsBase { @Test public void testBringTaskToFrontOnSecondaryDisplay() { final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, - false /* mockGetLaunchStack */); + false /* mockGetRootTask */); // Create a secondary display with an activity. final TestDisplayContent secondaryDisplay = @@ -943,7 +943,7 @@ public class ActivityStarterTests extends WindowTestsBase { @Test public void testReparentTopFocusedActivityToSecondaryDisplay() { final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, - false /* mockGetLaunchStack */); + false /* mockGetRootTask */); // Create a secondary display at bottom. final TestDisplayContent secondaryDisplay = addNewDisplayContentAt(POSITION_BOTTOM); @@ -1076,7 +1076,7 @@ public class ActivityStarterTests extends WindowTestsBase { @Test public void testTargetStackInSplitScreen() { final ActivityStarter starter = - prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetLaunchStack */); + prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */); final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityOptions options = ActivityOptions.makeBasic(); final ActivityRecord[] outActivity = new ActivityRecord[1]; diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 2ea7fdaf6348..eb6395b46120 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -422,6 +423,7 @@ public class AppTransitionTests extends WindowTestsBase { public void testActivityRecordReparentToTaskFragment() { final ActivityRecord activity = createActivityRecord(mDc); final SurfaceControl activityLeash = mock(SurfaceControl.class); + doNothing().when(activity).setDropInputMode(anyInt()); activity.setVisibility(true); activity.setSurfaceControl(activityLeash); final Task task = activity.getTask(); 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 3d95ec599b1b..8e990f7763c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -77,7 +77,6 @@ import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -108,6 +107,7 @@ import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.hardware.display.VirtualDisplay; import android.metrics.LogMaker; import android.os.Binder; @@ -1939,12 +1939,10 @@ public class DisplayContentTests extends WindowTestsBase { // Preparation: Simulate snapshot IME surface. spyOn(mWm.mTaskSnapshotController); - doReturn(mock(SurfaceControl.ScreenshotHardwareBuffer.class)).when( - mWm.mTaskSnapshotController).snapshotImeFromAttachedTask(any()); - final SurfaceControl imeSurface = mock(SurfaceControl.class); - spyOn(imeSurface); - doReturn(true).when(imeSurface).isValid(); - doReturn(imeSurface).when(mDisplayContent).createImeSurface(any(), any()); + SurfaceControl.ScreenshotHardwareBuffer mockHwBuffer = mock( + SurfaceControl.ScreenshotHardwareBuffer.class); + doReturn(mock(HardwareBuffer.class)).when(mockHwBuffer).getHardwareBuffer(); + doReturn(mockHwBuffer).when(mWm.mTaskSnapshotController).snapshotImeFromAttachedTask(any()); // Preparation: Simulate snapshot Task. ActivityRecord act1 = createActivityRecord(mDisplayContent); @@ -1970,21 +1968,18 @@ public class DisplayContentTests extends WindowTestsBase { final WindowState appWin2 = createWindow(null, TYPE_BASE_APPLICATION, act2, "appWin2"); appWin2.setHasSurface(true); assertTrue(appWin2.canBeImeTarget()); - doReturn(true).when(appWin1).isAnimating(PARENTS | TRANSITION, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); + doReturn(true).when(appWin1).isClosing(); + doReturn(true).when(appWin1).inAppOrRecentsTransition(); // Test step 3: Verify appWin2 will be the next IME target and the IME snapshot surface will - // be shown at this time. - final Transaction t = mDisplayContent.getPendingTransaction(); - spyOn(t); + // be attached and shown on the display at this time. mDisplayContent.computeImeTarget(true); assertEquals(appWin2, mDisplayContent.getImeTarget(IME_TARGET_LAYERING)); assertTrue(mDisplayContent.shouldImeAttachedToApp()); - verify(mDisplayContent, atLeast(1)).attachAndShowImeScreenshotOnTarget(); + verify(mDisplayContent, atLeast(1)).showImeScreenshot(); verify(mWm.mTaskSnapshotController).snapshotImeFromAttachedTask(appWin1.getTask()); assertNotNull(mDisplayContent.mImeScreenshot); - verify(t).show(mDisplayContent.mImeScreenshot); } @UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true) diff --git a/services/tests/wmtests/src/com/android/server/wm/MockSurfaceControlBuilder.java b/services/tests/wmtests/src/com/android/server/wm/MockSurfaceControlBuilder.java index 66139e6b483a..6a39d5608a19 100644 --- a/services/tests/wmtests/src/com/android/server/wm/MockSurfaceControlBuilder.java +++ b/services/tests/wmtests/src/com/android/server/wm/MockSurfaceControlBuilder.java @@ -20,6 +20,8 @@ import static org.mockito.Mockito.mock; import android.view.SurfaceControl; +import org.mockito.Mockito; + /** * Stubbed {@link SurfaceControl.Builder} class that returns a mocked SurfaceControl instance * that can be used for unit testing. @@ -32,6 +34,8 @@ class MockSurfaceControlBuilder extends SurfaceControl.Builder { @Override public SurfaceControl build() { - return mock(SurfaceControl.class); + SurfaceControl mockSurfaceControl = mock(SurfaceControl.class); + Mockito.doReturn(true).when(mockSurfaceControl).isValid(); + return mockSurfaceControl; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java index 19247604ad10..9d2eb26f5f21 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java @@ -101,14 +101,63 @@ public class RefreshRatePolicyTest extends WindowTestsBase { assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); - mPolicy.addNonHighRefreshRatePackage("com.android.test"); + mPolicy.addRefreshRateRangeForPackage("com.android.test", + LOW_REFRESH_RATE, LOW_REFRESH_RATE); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); - mPolicy.removeNonHighRefreshRatePackage("com.android.test"); + mPolicy.removeRefreshRateRangeForPackage("com.android.test"); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + } + + @Test + public void testCameraRange() { + final WindowState cameraUsingWindow = createWindow("cameraUsingWindow"); + cameraUsingWindow.mAttrs.packageName = "com.android.test"; + parcelLayoutParams(cameraUsingWindow); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + mPolicy.addRefreshRateRangeForPackage("com.android.test", + LOW_REFRESH_RATE, MID_REFRESH_RATE); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(LOW_REFRESH_RATE, + mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(MID_REFRESH_RATE, + mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + mPolicy.removeRefreshRateRangeForPackage("com.android.test"); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + } + + @Test + public void testCameraRange_OutOfRange() { + final WindowState cameraUsingWindow = createWindow("cameraUsingWindow"); + cameraUsingWindow.mAttrs.packageName = "com.android.test"; + parcelLayoutParams(cameraUsingWindow); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + mPolicy.addRefreshRateRangeForPackage("com.android.test", + LOW_REFRESH_RATE - 10, HI_REFRESH_RATE + 10); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(LOW_REFRESH_RATE, + mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(HI_REFRESH_RATE, + mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + mPolicy.removeRefreshRateRangeForPackage("com.android.test"); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); @@ -162,7 +211,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase { overrideWindow.mAttrs.packageName = "com.android.test"; overrideWindow.mAttrs.preferredDisplayModeId = HI_MODE_ID; parcelLayoutParams(overrideWindow); - mPolicy.addNonHighRefreshRatePackage("com.android.test"); + mPolicy.addRefreshRateRangeForPackage("com.android.test", + LOW_REFRESH_RATE, LOW_REFRESH_RATE); assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow)); assertEquals(HI_REFRESH_RATE, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE); @@ -178,7 +228,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase { overrideWindow.mAttrs.packageName = "com.android.test"; overrideWindow.mAttrs.preferredRefreshRate = HI_REFRESH_RATE; parcelLayoutParams(overrideWindow); - mPolicy.addNonHighRefreshRatePackage("com.android.test"); + mPolicy.addRefreshRateRangeForPackage("com.android.test", + LOW_REFRESH_RATE, LOW_REFRESH_RATE); assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); assertEquals(HI_REFRESH_RATE, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE); @@ -257,7 +308,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase { cameraUsingWindow.mAttrs.packageName = "com.android.test"; parcelLayoutParams(cameraUsingWindow); - mPolicy.addNonHighRefreshRatePackage("com.android.test"); + mPolicy.addRefreshRateRangeForPackage("com.android.test", + LOW_REFRESH_RATE, LOW_REFRESH_RATE); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); assertEquals(LOW_REFRESH_RATE, diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index acceadf8c499..99ba3b806d61 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -949,7 +949,7 @@ public class RootWindowContainerTests extends WindowTestsBase { LaunchParamsController.LaunchParams launchParams = new LaunchParamsController.LaunchParams(); launchParams.mPreferredTaskDisplayArea = taskDisplayArea; - Task root = mRootWindowContainer.getLaunchRootTask(null /* r */, null /* options */, + Task root = mRootWindowContainer.getOrCreateRootTask(null /* r */, null /* options */, null /* candidateTask */, null /* sourceTask */, true /* onTop */, launchParams, 0 /* launchParams */); assertEquals(taskDisplayArea, root.getTaskDisplayArea()); @@ -957,7 +957,7 @@ public class RootWindowContainerTests extends WindowTestsBase { // Making sure still getting the root task from the preferred TDA when passing in a // launching activity. ActivityRecord r = new ActivityBuilder(mAtm).build(); - root = mRootWindowContainer.getLaunchRootTask(r, null /* options */, + root = mRootWindowContainer.getOrCreateRootTask(r, null /* options */, null /* candidateTask */, null /* sourceTask */, true /* onTop */, launchParams, 0 /* launchParams */); assertEquals(taskDisplayArea, root.getTaskDisplayArea()); diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java index cb858845e03e..33b236669ec7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.server.wm.RunningTasks.FLAG_ALLOWED; import static com.android.server.wm.RunningTasks.FLAG_CROSS_USERS; import static com.android.server.wm.RunningTasks.FLAG_KEEP_INTENT_EXTRA; @@ -81,7 +82,9 @@ public class RunningTasksTest extends WindowTestsBase { rootTasks.add(task); }, false /* traverseTopToBottom */); for (int i = 0; i < numTasks; i++) { - createTask(rootTasks.get(i % numStacks), ".Task" + i, i, activeTime++, null); + final Task task = + createTask(rootTasks.get(i % numStacks), ".Task" + i, i, activeTime++, null); + doReturn(false).when(task).isVisible(); } // Ensure that the latest tasks were returned in order of decreasing last active time, @@ -158,6 +161,36 @@ public class RunningTasksTest extends WindowTestsBase { } } + @Test + public void testUpdateLastActiveTimeOfVisibleTasks() { + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build(); + final int numTasks = 10; + final ArrayList<Task> tasks = new ArrayList<>(); + for (int i = 0; i < numTasks; i++) { + final Task task = createTask(null, ".Task" + i, i, i, null); + doReturn(false).when(task).isVisible(); + tasks.add(task); + } + + final Task visibleTask = tasks.get(0); + doReturn(true).when(visibleTask).isVisible(); + + final Task focusedTask = tasks.get(1); + doReturn(true).when(focusedTask).isVisible(); + doReturn(true).when(focusedTask).isFocused(); + + // Ensure that the last active time of visible tasks were updated while the focused one had + // the largest last active time. + final int numFetchTasks = 5; + final ArrayList<RunningTaskInfo> fetchTasks = new ArrayList<>(); + mRunningTasks.getTasks(numFetchTasks, fetchTasks, + FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA, mRootWindowContainer, + -1 /* callingUid */, PROFILE_IDS); + assertThat(fetchTasks).hasSize(numFetchTasks); + assertEquals(fetchTasks.get(0).id, focusedTask.mTaskId); + assertEquals(fetchTasks.get(1).id, visibleTask.mTaskId); + } + /** * Create a task with a single activity in it, with the given last active time. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index 5ea8fd373f0b..d27581865e26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -23,6 +23,7 @@ import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.os.IBinder; import android.os.Parcel; import android.view.InputWindowHandle; @@ -282,6 +283,13 @@ public class StubTransaction extends SurfaceControl.Transaction { } @Override + @NonNull + public SurfaceControl.Transaction setBuffer(@NonNull SurfaceControl sc, + @Nullable HardwareBuffer buffer) { + return this; + } + + @Override public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) { return this; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index fe41734d0232..636c6bc77e0a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -146,6 +146,27 @@ public class TaskTests extends WindowTestsBase { } @Test + public void testRemoveContainer_multipleNestedTasks() { + final Task rootTask = createTask(mDisplayContent); + rootTask.mCreatedByOrganizer = true; + final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); + final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); + final ActivityRecord activity1 = createActivityRecord(task1); + final ActivityRecord activity2 = createActivityRecord(task2); + activity1.setVisible(false); + + // All activities under the root task should be finishing. + rootTask.remove(true /* withTransition */, "test"); + assertTrue(activity1.finishing); + assertTrue(activity2.finishing); + + // After all activities activities are destroyed, the root task should also be removed. + activity1.removeImmediately(); + activity2.removeImmediately(); + assertFalse(rootTask.isAttached()); + } + + @Test public void testRemoveContainer_deferRemoval() { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 68e90e1c00d3..08320f8c423f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -161,7 +161,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testDismissKeyguardCanWakeUp() { doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString()); - doReturn(true).when(mWm.mAtmService).isDreaming(); + doReturn(true).when(mWm.mAtmService.mKeyguardController).isShowingDream(); doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString()); mWm.dismissKeyguard(null, "test-dismiss-keyguard"); verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 84bd98347dab..a597fc69a340 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -97,6 +97,7 @@ final class HotwordDetectionConnection { private static final Duration MAX_UPDATE_TIMEOUT_DURATION = Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS); private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour + private static final int MAX_ISOLATED_PROCESS_NUMBER = 10; private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool(); // TODO: This may need to be a Handler(looper) @@ -772,7 +773,8 @@ final class HotwordDetectionConnection { ServiceConnection createLocked() { ServiceConnection connection = new ServiceConnection(mContext, mIntent, mBindingFlags, mUser, - IHotwordDetectionService.Stub::asInterface, ++mRestartCount); + IHotwordDetectionService.Stub::asInterface, + mRestartCount++ % MAX_ISOLATED_PROCESS_NUMBER); connection.connect(); updateAudioFlinger(connection, mAudioFlinger); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java new file mode 100644 index 000000000000..de0b9606045e --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.voiceinteraction; + +import com.android.internal.util.FrameworkStatsLog; + +/** + * A utility class for logging hotword statistics event. + */ +public final class HotwordMetricsLogger { + + private HotwordMetricsLogger() { + // Class only contains static utility functions, and should not be instantiated + } + + /** + * Logs information related to create hotword detector. + */ + public static void writeDetectorCreateEvent(int detectorType, boolean isCreated, int uid) { + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_CREATE_REQUESTED, detectorType, + isCreated, uid); + } + + /** + * Logs information related to hotword detection service init result. + */ + public static void writeServiceInitResultEvent(int detectorType, int result) { + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED, + detectorType, result); + } + + /** + * Logs information related to hotword detection service restarting. + */ + public static void writeServiceRestartEvent(int detectorType, int reason) { + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED, + detectorType, reason); + } + + /** + * Logs information related to keyphrase trigger. + */ + public static void writeKeyphraseTriggerEvent(int detectorType, int result) { + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED, + detectorType, result); + } + + /** + * Logs information related to hotword detector events. + */ + public static void writeDetectorEvent(int detectorType, int event, int uid) { + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS, + detectorType, event, uid); + } +} diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 27d423b3bc1e..bce6809ef32d 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -3171,9 +3171,14 @@ public abstract class ConnectionService extends Service { * * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}. * + * @param connectionManagerPhoneAccount The connection manager account to use for managing + * this call + * @param request Details about the outgoing call + * @return The {@code Connection} object to satisfy this call, or the result of an invocation + * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call * @hide */ - @SystemApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public @Nullable Connection onCreateUnknownConnection( @NonNull PhoneAccountHandle connectionManagerPhoneAccount, diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 0aaafef492bf..b6cacaf9f289 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1976,7 +1976,7 @@ public class SubscriptionManager { */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void addSubscriptionInfoRecord(@NonNull String uniqueId, @Nullable String displayName, - int slotIndex, int subscriptionType) { + int slotIndex, @SubscriptionType int subscriptionType) { if (VDBG) { logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId + ", displayName:" + displayName + ", slotIndex:" + slotIndex @@ -2012,7 +2012,8 @@ public class SubscriptionManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public void removeSubscriptionInfoRecord(@NonNull String uniqueId, int subscriptionType) { + public void removeSubscriptionInfoRecord(@NonNull String uniqueId, + @SubscriptionType int subscriptionType) { if (VDBG) { logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId + ", subscriptionType: " + subscriptionType); diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index ec734716f6e4..77d4837ccfb6 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -50,12 +50,13 @@ public class DataServiceCallback { */ @Retention(RetentionPolicy.SOURCE) @IntDef({RESULT_SUCCESS, RESULT_ERROR_UNSUPPORTED, RESULT_ERROR_INVALID_ARG, RESULT_ERROR_BUSY, - RESULT_ERROR_ILLEGAL_STATE}) + RESULT_ERROR_ILLEGAL_STATE, RESULT_ERROR_TEMPORARILY_UNAVAILABLE, + RESULT_ERROR_INVALID_RESPONSE}) public @interface ResultCode {} /** Request is completed successfully */ public static final int RESULT_SUCCESS = 0; - /** Request is not support */ + /** Request is not supported */ public static final int RESULT_ERROR_UNSUPPORTED = 1; /** Request contains invalid arguments */ public static final int RESULT_ERROR_INVALID_ARG = 2; @@ -68,6 +69,11 @@ public class DataServiceCallback { * @hide */ public static final int RESULT_ERROR_TEMPORARILY_UNAVAILABLE = 5; + /** + * Request failed to complete due to an invalid response. + * @hide + */ + public static final int RESULT_ERROR_INVALID_RESPONSE = 6; private final IDataServiceCallback mCallback; @@ -255,6 +261,8 @@ public class DataServiceCallback { return "RESULT_ERROR_ILLEGAL_STATE"; case RESULT_ERROR_TEMPORARILY_UNAVAILABLE: return "RESULT_ERROR_TEMPORARILY_UNAVAILABLE"; + case RESULT_ERROR_INVALID_RESPONSE: + return "RESULT_ERROR_INVALID_RESPONSE"; default: return "Unknown(" + resultCode + ")"; } diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java index 493ad5e473a5..1ccb4645a699 100644 --- a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java +++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java @@ -48,6 +48,9 @@ public class SipDelegateAidlWrapper implements DelegateStateCallback, DelegateMe @Override public void sendMessage(SipMessage sipMessage, long configVersion) { SipDelegate d = mDelegate; + if (d == null) { + return; + } final long token = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> d.sendMessage(sipMessage, configVersion)); @@ -59,6 +62,9 @@ public class SipDelegateAidlWrapper implements DelegateStateCallback, DelegateMe @Override public void notifyMessageReceived(String viaTransactionId) { SipDelegate d = mDelegate; + if (d == null) { + return; + } final long token = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> d.notifyMessageReceived(viaTransactionId)); @@ -71,6 +77,9 @@ public class SipDelegateAidlWrapper implements DelegateStateCallback, DelegateMe @Override public void notifyMessageReceiveError(String viaTransactionId, int reason) { SipDelegate d = mDelegate; + if (d == null) { + return; + } final long token = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> d.notifyMessageReceiveError(viaTransactionId, reason)); @@ -83,6 +92,9 @@ public class SipDelegateAidlWrapper implements DelegateStateCallback, DelegateMe @Override public void cleanupSession(String callId) { SipDelegate d = mDelegate; + if (d == null) { + return; + } final long token = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> d.cleanupSession(callId)); diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java index 18f962398fb8..bf8bd14645a0 100644 --- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java @@ -18,11 +18,13 @@ package com.google.android.test.handwritingime; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; import android.util.Log; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.FrameLayout; +import android.widget.TextView; import android.widget.Toast; import java.util.Random; @@ -79,6 +81,14 @@ public class HandwritingIme extends InputMethodService { view.setPadding(0, 0, 0, 0); view.addView(inner, new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, height)); + TextView text = new TextView(this); + text.setText("Handwriting IME"); + text.setTextSize(13f); + text.setTextColor(getColor(android.R.color.white)); + text.setGravity(Gravity.CENTER); + text.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, height)); + view.addView(text); inner.setBackgroundColor(0xff0110fe); // blue return view; diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java index 4ffdc9211f1d..87a5b900cc18 100644 --- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java @@ -33,6 +33,7 @@ class InkView extends View { private static final long FINISH_TIMEOUT = 2500; private final HandwritingIme.HandwritingFinisher mHwCanceller; private final HandwritingIme.StylusConsumer mConsumer; + private final int mTopInset; private Paint mPaint; private Path mPath; private float mX, mY; @@ -63,6 +64,7 @@ class InkView extends View { setLayoutParams(new ViewGroup.LayoutParams( metrics.getBounds().width() - insets.left - insets.right, metrics.getBounds().height() - insets.top - insets.bottom)); + mTopInset = insets.top; } @Override @@ -74,12 +76,14 @@ class InkView extends View { } private void stylusStart(float x, float y) { + y = y - mTopInset; mPath.moveTo(x, y); mX = x; mY = y; } private void stylusMove(float x, float y) { + y = y - mTopInset; float dx = Math.abs(x - mX); float dy = Math.abs(y - mY); if (mPath.isEmpty()) { diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml index c94152da2bf6..68bc1f69628f 100644 --- a/tests/TrustTests/AndroidManifest.xml +++ b/tests/TrustTests/AndroidManifest.xml @@ -23,7 +23,9 @@ <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" /> <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> + <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" /> <uses-permission android:name="android.permission.TRUST_LISTENER" /> diff --git a/tests/TrustTests/TEST_MAPPING b/tests/TrustTests/TEST_MAPPING new file mode 100644 index 000000000000..b9c46bfbba8c --- /dev/null +++ b/tests/TrustTests/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "TrustTests", + "options": [ + { + "include-filter": "android.trust.test" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt index 83fc28fee818..8f200a64450e 100644 --- a/tests/TrustTests/src/android/trust/test/LockUserTest.kt +++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt @@ -25,7 +25,6 @@ import android.util.Log import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain @@ -49,7 +48,6 @@ class LockUserTest { .around(lockStateTrackingRule) .around(trustAgentRule) - @Ignore("Causes issues with subsequent tests") // TODO: Enable test @Test fun lockUser_locksTheDevice() { Log.i(TAG, "Locking user") diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt index bc100ba03639..006525d857ac 100644 --- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt @@ -20,6 +20,8 @@ import android.content.Context import android.util.Log import android.view.WindowManagerGlobal import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.google.common.truth.Truth.assertWithMessage @@ -32,6 +34,7 @@ import org.junit.runners.model.Statement */ class ScreenLockRule : TestRule { private val context: Context = getApplicationContext() + private val uiDevice = UiDevice.getInstance(getInstrumentation()) private val windowManager = WindowManagerGlobal.getWindowManagerService() private val lockPatternUtils = LockPatternUtils(context) private var instantLockSavedValue = false @@ -48,19 +51,21 @@ class ScreenLockRule : TestRule { } finally { removeScreenLock() revertLockOnPowerButton() + verifyKeyguardDismissed() } } } private fun verifyNoScreenLockAlreadySet() { assertWithMessage("Screen Lock must not already be set on device") - .that(lockPatternUtils.isSecure(context.userId)) - .isFalse() + .that(lockPatternUtils.isSecure(context.userId)) + .isFalse() } private fun verifyKeyguardDismissed() { val maxWaits = 30 var waitCount = 0 + while (windowManager.isKeyguardLocked && waitCount < maxWaits) { Log.i(TAG, "Keyguard still showing; attempting to dismiss and wait 50ms ($waitCount)") windowManager.dismissKeyguard(null, null) @@ -68,19 +73,19 @@ class ScreenLockRule : TestRule { waitCount++ } assertWithMessage("Keyguard should be unlocked") - .that(windowManager.isKeyguardLocked) - .isFalse() + .that(windowManager.isKeyguardLocked) + .isFalse() } private fun setScreenLock() { lockPatternUtils.setLockCredential( - LockscreenCredential.createPin(PIN), - LockscreenCredential.createNone(), - context.userId + LockscreenCredential.createPin(PIN), + LockscreenCredential.createNone(), + context.userId ) assertWithMessage("Screen Lock should now be set") - .that(lockPatternUtils.isSecure(context.userId)) - .isTrue() + .that(lockPatternUtils.isSecure(context.userId)) + .isTrue() Log.i(TAG, "Device PIN set to $PIN") } @@ -90,14 +95,25 @@ class ScreenLockRule : TestRule { } private fun removeScreenLock() { - lockPatternUtils.setLockCredential( - LockscreenCredential.createNone(), - LockscreenCredential.createPin(PIN), - context.userId - ) - Log.i(TAG, "Device PIN cleared; waiting 50 ms then dismissing Keyguard") - Thread.sleep(50) - windowManager.dismissKeyguard(null, null) + var lockCredentialUnset = lockPatternUtils.setLockCredential( + LockscreenCredential.createNone(), + LockscreenCredential.createPin(PIN), + context.userId) + Thread.sleep(100) + assertWithMessage("Lock screen credential should be unset") + .that(lockCredentialUnset) + .isTrue() + + lockPatternUtils.setLockScreenDisabled(true, context.userId) + Thread.sleep(100) + assertWithMessage("Lockscreen needs to be disabled") + .that(lockPatternUtils.isLockScreenDisabled(context.userId)) + .isTrue() + + // this is here because somehow it helps the keyguard not get stuck + uiDevice.sleep() + Thread.sleep(500) // delay added to avoid initiating camera by double clicking power + uiDevice.wakeUp() } private fun revertLockOnPowerButton() { diff --git a/tools/aapt2/tools/finalize_res.py b/tools/aapt2/tools/finalize_res.py new file mode 100755 index 000000000000..0e4d865bc890 --- /dev/null +++ b/tools/aapt2/tools/finalize_res.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Finalize resource values in <staging-public-group> tags +and convert those to <staging-public-group-final> + +Usage: $ANDROID_BUILD_TOP/frameworks/base/tools/aapt2/tools/finalize_res.py \ + $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-staging.xml \ + $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-final.xml +""" + +import re +import sys + +resTypes = ["attr", "id", "style", "string", "dimen", "color", "array", "drawable", "layout", + "anim", "animator", "interpolator", "mipmap", "integer", "transition", "raw", "bool", + "fraction"] + +_type_ids = {} +_type = "" + +_lowest_staging_first_id = 0x01FFFFFF + +""" + Created finalized <public> declarations for staging resources, ignoring them if they've been + prefixed with removed_. The IDs are assigned without holes starting from the last ID for that + type currently finalized in public-final.xml. +""" +def finalize_item(raw): + name = raw.group(1) + if re.match(r'_*removed.+', name): + return "" + id = _type_ids[_type] + _type_ids[_type] += 1 + return ' <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8)) + + +""" + Finalizes staging-public-groups if they have any entries in them. Also keeps track of the + lowest first-id of the non-empty groups so that the next release's staging-public-groups can + be assigned the next down shifted first-id. +""" +def finalize_group(raw): + global _type, _lowest_staging_first_id + _type = raw.group(1) + id = int(raw.group(2), 16) + _type_ids[_type] = _type_ids.get(_type, id) + (res, count) = re.subn(' {0,4}<public name="(.+?)" */>\n', finalize_item, raw.group(3)) + if count > 0: + res = raw.group(0).replace("staging-public-group", + "staging-public-group-final") + '\n' + res + _lowest_staging_first_id = min(id, _lowest_staging_first_id) + return res + +""" + Collects the max ID for each resType so that the new IDs can be assigned afterwards +""" +def collect_ids(raw): + for m in re.finditer(r'<public type="(.+?)" name=".+?" id="(.+?)" />', raw): + type = m.group(1) + id = int(m.group(2), 16) + _type_ids[type] = max(id + 1, _type_ids.get(type, 0)) + + +with open(sys.argv[1], "r+") as stagingFile: + with open(sys.argv[2], "r+") as finalFile: + existing = finalFile.read() + # Cut out the closing resources tag so that it can be concatenated easily later + existing = "\n".join(existing.rsplit("</resources>", 1)) + + # Collect the IDs from the existing already finalized resources + collect_ids(existing) + + staging = stagingFile.read() + stagingSplit = staging.rsplit("<resources>") + staging = stagingSplit[1] + staging = re.sub( + r'<staging-public-group type="(.+?)" first-id="(.+?)">(.+?)</staging-public-group>', + finalize_group, staging, flags=re.DOTALL) + staging = re.sub(r' *\n', '\n', staging) + staging = re.sub(r'\n{3,}', '\n\n', staging) + + # First write the existing finalized declarations and then append the new stuff + finalFile.seek(0) + finalFile.write(existing.strip("\n")) + finalFile.write("\n\n") + finalFile.write(staging.strip("\n")) + finalFile.write("\n") + finalFile.truncate() + + stagingFile.seek(0) + # Include the documentation from public-staging.xml that was previously split out + stagingFile.write(stagingSplit[0]) + # Write the next platform header + stagingFile.write("<resources>\n\n") + stagingFile.write(" <!-- ===============================================================\n") + stagingFile.write(" Resources added in version NEXT of the platform\n\n") + stagingFile.write(" NOTE: After this version of the platform is forked, changes cannot be made to the root\n") + stagingFile.write(" branch's groups for that release. Only merge changes to the forked platform branch.\n") + stagingFile.write(" =============================================================== -->\n") + stagingFile.write(" <eat-comment/>\n\n") + + # Seed the next release's staging-public-groups as empty declarations, + # so its easy for another developer to expose a new public resource + nextId = _lowest_staging_first_id - 0x00010000 + for resType in resTypes: + stagingFile.write(' <staging-public-group type="%s" first-id="%s">\n' + ' </staging-public-group>\n\n' % + (resType, '0x{0:0{1}x}'.format(nextId, 8))) + nextId -= 0x00010000 + + # Close the resources tag and truncate, since the file will be shorter than the previous + stagingFile.write("</resources>\n") + stagingFile.truncate() diff --git a/tools/finalize_res/finalize_res.py b/tools/finalize_res/finalize_res.py deleted file mode 100755 index aaf01875024e..000000000000 --- a/tools/finalize_res/finalize_res.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -#-*- coding: 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. - -""" -Finalize resource values in <staging-public-group> tags - -Usage: finalize_res.py core/res/res/values/public.xml public_finalized.xml -""" - -import re, sys, codecs - -def finalize_item(raw): - global _type, _id - _id += 1 - return '<public type="%s" name="%s" id="%s" />' % (_type, raw.group(1), '0x{0:0{1}x}'.format(_id-1,8)) - -def finalize_group(raw): - global _type, _id - _type = raw.group(1) - _id = int(raw.group(2), 16) - return re.sub(r'<public name="(.+?)" */>', finalize_item, raw.group(3)) - -with open(sys.argv[1]) as f: - raw = f.read() - raw = re.sub(r'<staging-public-group type="(.+?)" first-id="(.+?)">(.+?)</staging-public-group>', finalize_group, raw, flags=re.DOTALL) - with open(sys.argv[2], "w") as f: - f.write(raw) diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt index 1aec9b812e61..2e60f64b21e8 100644 --- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt +++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt @@ -21,8 +21,6 @@ import com.android.codegen.BASE_BUILDER_CLASS import com.android.codegen.CANONICAL_BUILDER_CLASS import com.android.codegen.CODEGEN_NAME import com.android.codegen.CODEGEN_VERSION -import com.sun.tools.javac.code.Symbol -import com.sun.tools.javac.code.Type import java.io.File import java.io.FileNotFoundException import javax.annotation.processing.AbstractProcessor @@ -33,6 +31,7 @@ import javax.lang.model.element.AnnotationMirror import javax.lang.model.element.Element import javax.lang.model.element.ElementKind import javax.lang.model.element.TypeElement +import javax.lang.model.type.ExecutableType import javax.tools.Diagnostic private const val STALE_FILE_THRESHOLD_MS = 1000 @@ -102,14 +101,13 @@ class StaleDataclassProcessor: AbstractProcessor() { append(" ") append(elem.annotationMirrors.joinToString(" ", transform = { annotationToString(it) })) append(" ") - if (elem is Symbol) { - if (elem.type is Type.MethodType) { - append((elem.type as Type.MethodType).returnType) - } else { - append(elem.type) - } - append(" ") + val type = elem.asType() + if (type is ExecutableType) { + append(type.returnType) + } else { + append(type) } + append(" ") append(elem) } } @@ -234,4 +232,4 @@ class StaleDataclassProcessor: AbstractProcessor() { override fun getSupportedSourceVersion(): SourceVersion { return SourceVersion.latest() } -}
\ No newline at end of file +} diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index 960447504d15..8630ec4b062d 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -963,6 +963,25 @@ public class WifiNl80211Manager { } /** + * Get the max number of SSIDs that the driver supports per scan. + * + * @param ifaceName Name of the interface. + */ + public int getMaxNumScanSsids(@NonNull String ifaceName) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName); + return 0; + } + try { + return scannerImpl.getMaxNumScanSsids(); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to getMaxNumScanSsids"); + } + return 0; + } + + /** * Return scan type for the parcelable {@link SingleScanSettings} */ private static int getScanType(@WifiAnnotations.ScanType int scanType) { |