diff options
89 files changed, 3539 insertions, 427 deletions
diff --git a/api/current.txt b/api/current.txt index 4115b3ae526a..f2755c9f62d6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6853,6 +6853,7 @@ package android.app.admin { method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName); 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 java.util.List<java.lang.String> getProtectedPackages(@NonNull android.content.ComponentName); method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName); method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName); @@ -6979,6 +6980,7 @@ package android.app.admin { method public boolean setPermittedAccessibilityServices(@NonNull android.content.ComponentName, java.util.List<java.lang.String>); 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 setProfileEnabled(@NonNull android.content.ComponentName); method public void setProfileName(@NonNull android.content.ComponentName, String); method public void setProtectedPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); @@ -7014,6 +7016,7 @@ package android.app.admin { field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE"; field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED"; field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE"; + field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE"; field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED"; field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE"; @@ -7137,6 +7140,8 @@ package android.app.admin { field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2 field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1 field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0 + field public static final int PERSONAL_APPS_NOT_SUSPENDED = 0; // 0x0 + field public static final int PERSONAL_APPS_SUSPENDED_EXPLICITLY = 1; // 0x1 field public static final String POLICY_DISABLE_CAMERA = "policy_disable_camera"; field public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture"; field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1 @@ -9965,6 +9970,7 @@ package android.content { method public abstract android.content.Context createDisplayContext(@NonNull android.view.Display); method @NonNull public android.content.Context createFeatureContext(@Nullable String); method public abstract android.content.Context createPackageContext(String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public android.content.Context createWindowContext(int); method public abstract String[] databaseList(); method public abstract boolean deleteDatabase(String); method public abstract boolean deleteFile(String); @@ -9989,6 +9995,7 @@ package android.content { method public abstract java.io.File getDataDir(); method public abstract java.io.File getDatabasePath(String); method public abstract java.io.File getDir(String, int); + method @Nullable public android.view.Display getDisplay(); method @Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int); method @Nullable public abstract java.io.File getExternalCacheDir(); method public abstract java.io.File[] getExternalCacheDirs(); @@ -11771,6 +11778,7 @@ package android.content.pm { method @Nullable public CharSequence getAppLabel(); method @Nullable public String getAppPackageName(); method @NonNull public int[] getChildSessionIds(); + method public long getCreatedMillis(); method public int getInstallLocation(); method public int getInstallReason(); method @Nullable public String getInstallerPackageName(); @@ -24189,8 +24197,10 @@ package android.media { public final class AudioPlaybackCaptureConfiguration { method @NonNull public int[] getExcludeUids(); method @NonNull public int[] getExcludeUsages(); + method @NonNull public int[] getExcludeUserIds(); method @NonNull public int[] getMatchingUids(); method @NonNull public int[] getMatchingUsages(); + method @NonNull public int[] getMatchingUserIds(); method @NonNull public android.media.projection.MediaProjection getMediaProjection(); } @@ -24198,9 +24208,11 @@ package android.media { ctor public AudioPlaybackCaptureConfiguration.Builder(@NonNull android.media.projection.MediaProjection); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUid(int); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUsage(int); + method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder addMatchingUserId(int); method @NonNull public android.media.AudioPlaybackCaptureConfiguration build(); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUid(int); method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUsage(int); + method @NonNull public android.media.AudioPlaybackCaptureConfiguration.Builder excludeUserId(int); } public final class AudioPlaybackConfiguration implements android.os.Parcelable { @@ -54620,7 +54632,7 @@ package android.view { } public interface WindowManager extends android.view.ViewManager { - method public android.view.Display getDefaultDisplay(); + method @Deprecated public android.view.Display getDefaultDisplay(); method public void removeViewImmediate(android.view.View); } diff --git a/api/system-current.txt b/api/system-current.txt index 86b344001f42..0cd7d2a4bc1a 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -59,10 +59,12 @@ package android { field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; + field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY"; field @Deprecated public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL"; field public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"; + field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS"; field public static final String CONTROL_DISPLAY_COLOR_TRANSFORMS = "android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"; field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION"; field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; @@ -188,6 +190,7 @@ package android { field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; + field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS"; @@ -1716,7 +1719,7 @@ package android.bluetooth.le { package android.companion { public final class CompanionDeviceManager { - method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociated(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle); + method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle); } } @@ -2925,6 +2928,48 @@ package android.hardware.hdmi { } +package android.hardware.lights { + + public final class Light implements android.os.Parcelable { + method public int describeContents(); + method public int getId(); + method public int getOrdinal(); + method public int getType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR; + } + + public final class LightState implements android.os.Parcelable { + ctor public LightState(@ColorInt int); + method public int describeContents(); + method @ColorInt public int getColor(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR; + } + + public final class LightsManager { + method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public java.util.List<android.hardware.lights.Light> getLights(); + method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightsManager.LightsSession openSession(); + field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8 + } + + public final class LightsManager.LightsSession implements java.lang.AutoCloseable { + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void close(); + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void setLights(@NonNull android.hardware.lights.LightsRequest); + } + + public final class LightsRequest { + } + + public static final class LightsRequest.Builder { + ctor public LightsRequest.Builder(); + method @NonNull public android.hardware.lights.LightsRequest build(); + method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light); + method @NonNull public android.hardware.lights.LightsRequest.Builder setLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState); + } + +} + package android.hardware.location { public class ContextHubClient implements java.io.Closeable { @@ -4367,6 +4412,7 @@ package android.media.audiopolicy { field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2 field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1 field public static final int RULE_MATCH_UID = 4; // 0x4 + field public static final int RULE_MATCH_USERID = 8; // 0x8 } public static class AudioMixingRule.Builder { @@ -4387,9 +4433,11 @@ package android.media.audiopolicy { method public int getFocusDuckingBehavior(); method public int getStatus(); method public boolean removeUidDeviceAffinity(int); + method public boolean removeUserIdDeviceAffinity(int); method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setRegistration(String); method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); + method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); method public String toLogFriendlyString(); field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0 field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0 @@ -6260,7 +6308,7 @@ package android.net { public class NetworkKey implements android.os.Parcelable { ctor public NetworkKey(android.net.WifiKey); - method @Nullable public static android.net.NetworkKey createFromScanResult(@Nullable android.net.wifi.ScanResult); + method @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkKey> CREATOR; @@ -6311,12 +6359,12 @@ package android.net { } public class NetworkScoreManager { - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean clearScores() throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public void disableScoring() throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public String getActiveScorerPackage(); - method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException; - method @RequiresPermission("android.permission.REQUEST_NETWORK_SCORES") public boolean requestScores(@NonNull android.net.NetworkKey[]) throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, "android.permission.REQUEST_NETWORK_SCORES"}) public boolean setActiveScorer(String) throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public String getActiveScorerPackage(); + method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException; + method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public boolean requestScores(@NonNull java.util.Collection<android.net.NetworkKey>) throws java.lang.SecurityException; + method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean setActiveScorer(String) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS) public boolean updateScores(@NonNull android.net.ScoredNetwork[]) throws java.lang.SecurityException; field @Deprecated public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE"; field public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE"; @@ -6331,9 +6379,10 @@ package android.net { field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2 } - public static interface NetworkScoreManager.NetworkScoreCallback { - method public void clearScores(); - method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>); + public abstract static class NetworkScoreManager.NetworkScoreCallback { + ctor public NetworkScoreManager.NetworkScoreCallback(); + method public abstract void onScoresInvalidated(); + method public abstract void onScoresUpdated(@NonNull java.util.Collection<android.net.ScoredNetwork>); } public abstract class NetworkSpecifier { @@ -7501,6 +7550,7 @@ package android.net.wifi { public class WifiManager { method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinGlobal(boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void clearWifiConnectedNetworkScorer(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); diff --git a/api/test-current.txt b/api/test-current.txt index fd1fcec44e29..e2407d65ef5c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -10,6 +10,7 @@ package android { field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; + field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; @@ -704,7 +705,7 @@ package android.bluetooth { package android.companion { public final class CompanionDeviceManager { - method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociated(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle); + method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle); } } @@ -758,7 +759,6 @@ package android.content { method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int); method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public java.io.File getCrateDir(@NonNull String); - method public abstract android.view.Display getDisplay(); method public abstract int getDisplayId(); method public android.os.UserHandle getUser(); method public int getUserId(); @@ -779,7 +779,6 @@ package android.content { } public class ContextWrapper extends android.content.Context { - method public android.view.Display getDisplay(); method public int getDisplayId(); } @@ -1132,6 +1131,49 @@ package android.hardware.display { } +package android.hardware.lights { + + public final class Light implements android.os.Parcelable { + method public int describeContents(); + method public int getId(); + method public int getOrdinal(); + method public int getType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR; + } + + public final class LightState implements android.os.Parcelable { + ctor public LightState(@ColorInt int); + method public int describeContents(); + method @ColorInt public int getColor(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR; + } + + public final class LightsManager { + method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightState getLightState(@NonNull android.hardware.lights.Light); + method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public java.util.List<android.hardware.lights.Light> getLights(); + method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightsManager.LightsSession openSession(); + field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8 + } + + public final class LightsManager.LightsSession implements java.lang.AutoCloseable { + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void close(); + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void setLights(@NonNull android.hardware.lights.LightsRequest); + } + + public final class LightsRequest { + } + + public static final class LightsRequest.Builder { + ctor public LightsRequest.Builder(); + method @NonNull public android.hardware.lights.LightsRequest build(); + method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light); + method @NonNull public android.hardware.lights.LightsRequest.Builder setLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState); + } + +} + package android.location { public final class GnssClock implements android.os.Parcelable { @@ -1399,6 +1441,7 @@ package android.media.audiopolicy { field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2 field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1 field public static final int RULE_MATCH_UID = 4; // 0x4 + field public static final int RULE_MATCH_USERID = 8; // 0x8 } public static class AudioMixingRule.Builder { @@ -1419,9 +1462,11 @@ package android.media.audiopolicy { method public int getFocusDuckingBehavior(); method public int getStatus(); method public boolean removeUidDeviceAffinity(int); + method public boolean removeUserIdDeviceAffinity(int); method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setRegistration(String); method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); + method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>); method public String toLogFriendlyString(); field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0 field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0 diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index cd84310356b1..b7555ee1c04e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -19,7 +19,6 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.BroadcastReceiver; @@ -201,7 +200,7 @@ class ContextImpl extends Context { @UnsupportedAppUsage private @Nullable ClassLoader mClassLoader; - private final @Nullable IBinder mActivityToken; + private final @Nullable IBinder mToken; private final @NonNull UserHandle mUser; @@ -219,7 +218,7 @@ class ContextImpl extends Context { private final @NonNull ResourcesManager mResourcesManager; @UnsupportedAppUsage private @NonNull Resources mResources; - private @Nullable Display mDisplay; // may be null if default display + private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mFlags; @@ -244,6 +243,9 @@ class ContextImpl extends Context { private final Object mSync = new Object(); + private boolean mIsSystemOrSystemUiContext; + private boolean mIsUiContext; + @GuardedBy("mSync") private File mDatabasesDir; @GuardedBy("mSync") @@ -1883,6 +1885,9 @@ class ContextImpl extends Context { @Override public Object getSystemService(String name) { + if (isUiComponent(name) && !isUiContext()) { + Log.w(TAG, name + " should be accessed from Activity or other visual Context"); + } return SystemServiceRegistry.getSystemService(this, name); } @@ -1891,6 +1896,15 @@ class ContextImpl extends Context { return SystemServiceRegistry.getSystemServiceName(serviceClass); } + boolean isUiContext() { + return mIsSystemOrSystemUiContext || mIsUiContext; + } + + private static boolean isUiComponent(String name) { + return WINDOW_SERVICE.equals(name) || LAYOUT_INFLATER_SERVICE.equals(name) + || WALLPAPER_SERVICE.equals(name); + } + @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { @@ -2229,12 +2243,12 @@ class ContextImpl extends Context { LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mActivityToken, + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, null, mToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); final int displayId = getDisplayId(); - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2258,18 +2272,18 @@ class ContextImpl extends Context { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, null, - mActivityToken, user, flags, null, null); + mToken, user, flags, null, null); } LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); if (pi != null) { ContextImpl c = new ContextImpl(this, mMainThread, pi, mFeatureId, null, - mActivityToken, user, flags, null, null); + mToken, user, flags, null, null); final int displayId = getDisplayId(); - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2301,12 +2315,12 @@ class ContextImpl extends Context { final String[] paths = mPackageInfo.getSplitPaths(splitName); final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, - mFeatureId, splitName, mActivityToken, mUser, mFlags, classLoader, null); + mFeatureId, splitName, mToken, mUser, mFlags, classLoader, null); final int displayId = getDisplayId(); context.setResources(ResourcesManager.getInstance().getResources( - mActivityToken, + mToken, mPackageInfo.getResDir(), paths, mPackageInfo.getOverlayDirs(), @@ -2325,10 +2339,10 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, - mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null); + mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = getDisplayId(); - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2340,19 +2354,36 @@ class ContextImpl extends Context { } ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, - mSplitName, mActivityToken, mUser, mFlags, mClassLoader, null); + mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = display.getDisplayId(); - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; } @Override + public @NonNull WindowContext createWindowContext(int type) { + if (getDisplay() == null) { + throw new UnsupportedOperationException("WindowContext can only be created from " + + "other visual contexts, such as Activity or one created with " + + "Context#createDisplayContext(Display)"); + } + return new WindowContext(this, null /* token */, type); + } + + ContextImpl createBaseWindowContext(IBinder token) { + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, + mSplitName, token, mUser, mFlags, mClassLoader, null); + context.mIsUiContext = true; + return context; + } + + @Override public @NonNull Context createFeatureContext(@Nullable String featureId) { return new ContextImpl(this, mMainThread, mPackageInfo, featureId, mSplitName, - mActivityToken, mUser, mFlags, mClassLoader, null); + mToken, mUser, mFlags, mClassLoader, null); } @Override @@ -2360,7 +2391,7 @@ class ContextImpl extends Context { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName, - mActivityToken, mUser, flags, mClassLoader, null); + mToken, mUser, flags, mClassLoader, null); } @Override @@ -2368,7 +2399,7 @@ class ContextImpl extends Context { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; return new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName, - mActivityToken, mUser, flags, mClassLoader, null); + mToken, mUser, flags, mClassLoader, null); } @Override @@ -2394,8 +2425,6 @@ class ContextImpl extends Context { return (mFlags & Context.CONTEXT_IGNORE_SECURITY) != 0; } - @UnsupportedAppUsage - @TestApi @Override public Display getDisplay() { if (mDisplay == null) { @@ -2408,7 +2437,8 @@ class ContextImpl extends Context { @Override public int getDisplayId() { - return mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; + final Display display = getDisplay(); + return display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY; } @Override @@ -2518,6 +2548,7 @@ class ContextImpl extends Context { context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); + context.mIsSystemOrSystemUiContext = true; return context; } @@ -2535,6 +2566,7 @@ class ContextImpl extends Context { context.setResources(createResources(null, packageInfo, null, displayId, null, packageInfo.getCompatibilityInfo())); context.updateDisplay(displayId); + context.mIsSystemOrSystemUiContext = true; return context; } @@ -2584,6 +2616,7 @@ class ContextImpl extends Context { ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, activityInfo.splitName, activityToken, null, 0, classLoader, null); + context.mIsUiContext = true; // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; @@ -2629,7 +2662,7 @@ class ContextImpl extends Context { } mMainThread = mainThread; - mActivityToken = activityToken; + mToken = activityToken; mFlags = flags; if (user == null) { @@ -2649,6 +2682,7 @@ class ContextImpl extends Context { opPackageName = container.mOpPackageName; setResources(container.mResources); mDisplay = container.mDisplay; + mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext; } else { mBasePackageName = packageInfo.mPackageName; ApplicationInfo ainfo = packageInfo.getApplicationInfo(); @@ -2710,7 +2744,7 @@ class ContextImpl extends Context { @Override @UnsupportedAppUsage public IBinder getActivityToken() { - return mActivityToken; + return mToken; } private void checkMode(int mode) { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 3abd5094e11d..7f698653bef7 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -89,6 +89,7 @@ import android.hardware.hdmi.IHdmiControlService; import android.hardware.input.InputManager; import android.hardware.iris.IIrisService; import android.hardware.iris.IrisManager; +import android.hardware.lights.LightsManager; import android.hardware.location.ContextHubManager; import android.hardware.radio.RadioManager; import android.hardware.usb.IUsbManager; @@ -1275,6 +1276,13 @@ public final class SystemServiceRegistry { Context.DATA_LOADER_MANAGER_SERVICE); return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b)); }}); + registerService(Context.LIGHTS_SERVICE, LightsManager.class, + new CachedServiceFetcher<LightsManager>() { + @Override + public LightsManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new LightsManager(ctx); + }}); //TODO(b/136132412): refactor this: 1) merge IIncrementalManager.aidl and //IIncrementalManagerNative.aidl, 2) implement the binder interface in //IncrementalManagerService.java, 3) use JNI to call native functions diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java new file mode 100644 index 000000000000..22cc14bd5ed6 --- /dev/null +++ b/core/java/android/app/WindowContext.java @@ -0,0 +1,123 @@ +/* + * 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 android.app; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerImpl; + +/** + * {@link WindowContext} is a context for non-activity windows such as + * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} windows or system + * windows. Its resources and configuration are adjusted to the area of the display that will be + * used when a new window is added via {@link android.view.WindowManager.addView}. + * + * @see Context#createWindowContext(int) + * @hide + */ +// TODO(b/128338354): Handle config/display changes from server side. +public class WindowContext extends ContextWrapper { + private final WindowManagerImpl mWindowManager; + private final IWindowManager mWms; + private final IBinder mToken; + private final int mDisplayId; + private boolean mOwnsToken; + + /** + * Default constructor. Can either accept an existing token or generate one and registers it + * with the server if necessary. + * + * @param base Base {@link Context} for this new instance. + * @param token A valid {@link com.android.server.wm.WindowToken}. Pass {@code null} to generate + * one. + * @param type Window type to be used with this context. + * @hide + */ + public WindowContext(Context base, IBinder token, int type) { + super(null /* base */); + + mWms = WindowManagerGlobal.getWindowManagerService(); + if (token != null && !isWindowToken(token)) { + throw new IllegalArgumentException("Token must be registered to server."); + } + + final ContextImpl contextImpl = createBaseWindowContext(base, token); + attachBaseContext(contextImpl); + contextImpl.setOuterContext(this); + + mToken = token != null ? token : new Binder(); + mDisplayId = getDisplayId(); + mWindowManager = new WindowManagerImpl(this); + mWindowManager.setDefaultToken(mToken); + + // TODO(b/128338354): Obtain the correct config from WM and adjust resources. + if (token != null) { + mOwnsToken = false; + return; + } + try { + mWms.addWindowContextToken(mToken, type, mDisplayId, getPackageName()); + // TODO(window-context): remove token with a DeathObserver + } catch (RemoteException e) { + mOwnsToken = false; + throw e.rethrowFromSystemServer(); + } + mOwnsToken = true; + } + + /** Check if the passed window token is registered with the server. */ + private boolean isWindowToken(@NonNull IBinder token) { + try { + return mWms.isWindowToken(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + private static ContextImpl createBaseWindowContext(Context outer, IBinder token) { + final ContextImpl contextImpl = ContextImpl.getImpl(outer); + return contextImpl.createBaseWindowContext(token); + } + + @Override + public Object getSystemService(String name) { + if (WINDOW_SERVICE.equals(name)) { + return mWindowManager; + } + return super.getSystemService(name); + } + + @Override + protected void finalize() throws Throwable { + if (mOwnsToken) { + try { + mWms.removeWindowToken(mToken, mDisplayId); + mOwnsToken = false; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + super.finalize(); + } +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1bf6c99233e5..aeeabb7c1726 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2391,6 +2391,28 @@ public class DevicePolicyManager { "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE"; /** + * Return value for {@link #getPersonalAppsSuspendedReasons} when personal apps are not + * suspended. + */ + public static final int PERSONAL_APPS_NOT_SUSPENDED = 0; + + /** + * Flag for {@link #getPersonalAppsSuspendedReasons} return value. Set when personal + * apps are suspended by an admin explicitly via {@link #setPersonalAppsSuspended}. + */ + public static final int PERSONAL_APPS_SUSPENDED_EXPLICITLY = 1 << 0; + + /** + * @hide + */ + @IntDef(flag = true, prefix = { "PERSONAL_APPS_" }, value = { + PERSONAL_APPS_NOT_SUSPENDED, + PERSONAL_APPS_SUSPENDED_EXPLICITLY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PersonalAppSuspensionReason {} + + /** * Return true if the given administrator component is currently active (enabled) in the system. * * @param admin The administrator component to check for. @@ -4577,6 +4599,18 @@ public class DevicePolicyManager { = "android.app.action.START_ENCRYPTION"; /** + * Activity action: launch the DPC to check policy compliance. This intent is launched when + * the user taps on the notification about personal apps suspension. When handling this intent + * the DPC must check if personal apps should still be suspended and either unsuspend them or + * instruct the user on how to resolve the noncompliance causing the suspension. + * + * @see #setPersonalAppsSuspended + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHECK_POLICY_COMPLIANCE = + "android.app.action.CHECK_POLICY_COMPLIANCE"; + + /** * Broadcast action: notify managed provisioning that new managed user is created. * * @hide @@ -9334,6 +9368,16 @@ public class DevicePolicyManager { * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted. * + * NOTE: Starting from Android R, location-related permissions cannot be granted by the + * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the + * following permissions will return false: + * + * <ul> + * <li>{@code ACCESS_FINE_LOCATION}</li> + * <li>{@code ACCESS_BACKGROUND_LOCATION}</li> + * <li>{@code ACCESS_COARSE_LOCATION}</li> + * </ul> + * * @param admin Which profile or device owner this request is associated with. * @param packageName The application to grant or revoke a permission to. * @param permission The permission to grant or revoke. @@ -11665,4 +11709,48 @@ public class DevicePolicyManager { } return false; } + + /** + * Called by profile owner of an organization-owned managed profile to check whether + * personal apps are suspended. + * + * @return a bitmask of reasons for personal apps suspension or + * {@link #PERSONAL_APPS_NOT_SUSPENDED} if apps are not suspended. + * @see #setPersonalAppsSuspended + */ + public @PersonalAppSuspensionReason int getPersonalAppsSuspendedReasons( + @NonNull ComponentName admin) { + throwIfParentInstance("getPersonalAppsSuspendedReasons"); + if (mService != null) { + try { + return mService.getPersonalAppsSuspendedReasons(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return 0; + } + + /** + * Called by a profile owner of an organization-owned managed profile to suspend personal + * apps on the device. When personal apps are suspended the device can only be used for calls. + * + * <p>When personal apps are suspended, an ongoing notification about that is shown to the user. + * When the user taps the notification, system invokes {@link #ACTION_CHECK_POLICY_COMPLIANCE} + * in the profile owner package. Profile owner implementation that uses personal apps suspension + * must handle this intent. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with + * @param suspended Whether personal apps should be suspended. + */ + public void setPersonalAppsSuspended(@NonNull ComponentName admin, boolean suspended) { + throwIfParentInstance("setPersonalAppsSuspended"); + if (mService != null) { + try { + mService.setPersonalAppsSuspended(admin, suspended); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index e7667c0a1b4a..b9ffc4e214a6 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -470,4 +470,7 @@ interface IDevicePolicyManager { void setCommonCriteriaModeEnabled(in ComponentName admin, boolean enabled); boolean isCommonCriteriaModeEnabled(in ComponentName admin); + + int getPersonalAppsSuspendedReasons(in ComponentName admin); + void setPersonalAppsSuspended(in ComponentName admin, boolean suspended); } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 3107c6302775..d4b5b1aab532 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -269,7 +269,7 @@ public final class CompanionDeviceManager { @SystemApi @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) - public boolean isDeviceAssociated( + public boolean isDeviceAssociatedForWifiConnection( @NonNull String packageName, @NonNull MacAddress macAddress, @NonNull UserHandle user) { @@ -280,7 +280,7 @@ public final class CompanionDeviceManager { Objects.requireNonNull(macAddress, "mac address cannot be null"); Objects.requireNonNull(user, "user cannot be null"); try { - return mService.isDeviceAssociated( + return mService.isDeviceAssociatedForWifiConnection( packageName, macAddress.toString(), user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 2e1ff0be8577..b323f58aa827 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -40,5 +40,6 @@ interface ICompanionDeviceManager { boolean hasNotificationAccess(in ComponentName component); PendingIntent requestNotificationAccess(in ComponentName component); - boolean isDeviceAssociated(in String packageName, in String macAddress, int userId); + boolean isDeviceAssociatedForWifiConnection(in String packageName, in String macAddress, + int userId); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 7b0cdcf05846..ebc5e0e4aa31 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3480,6 +3480,7 @@ public abstract class Context { //@hide: TIME_DETECTOR_SERVICE, //@hide: TIME_ZONE_DETECTOR_SERVICE, PERMISSION_SERVICE, + LIGHTS_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -5121,6 +5122,15 @@ public abstract class Context { public static final String FILE_INTEGRITY_SERVICE = "file_integrity"; /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.hardware.lights.LightsManager} for controlling device lights. + * + * @see #getSystemService(String) + * @hide + */ + public static final String LIGHTS_SERVICE = "lights"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * @@ -5730,6 +5740,63 @@ public abstract class Context { public abstract Context createDisplayContext(@NonNull Display display); /** + * Creates a Context for a non-activity window. + * + * <p> + * A window context is a context that can be used to add non-activity windows, such as + * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY}. A window context + * must be created from a context that has an associated {@link Display}, such as + * {@link android.app.Activity Activity} or a context created with + * {@link #createDisplayContext(Display)}. + * + * <p> + * The window context is created with the appropriate {@link Configuration} for the area of the + * display that the windows created with it can occupy; it must be used when + * {@link android.view.LayoutInflater inflating} views, such that they can be inflated with + * proper {@link Resources}. + * + * Below is a sample code to <b>add an application overlay window on the primary display:<b/> + * <pre class="prettyprint"> + * ... + * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class); + * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY); + * final Context windowContext = anyContext.createDisplayContext(primaryDisplay) + * .createWindowContext(TYPE_APPLICATION_OVERLAY); + * final View overlayView = Inflater.from(windowContext).inflate(someLayoutXml, null); + * + * // WindowManager.LayoutParams initialization + * ... + * mParams.type = TYPE_APPLICATION_OVERLAY; + * ... + * + * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); + * </pre> + * + * <p> + * This context's configuration and resources are adjusted to a display area where the windows + * with provided type will be added. <b>Note that all windows associated with the same context + * will have an affinity and can only be moved together between different displays or areas on a + * display.</b> If there is a need to add different window types, or non-associated windows, + * separate Contexts should be used. + * </p> + * + * @param type Window type in {@link WindowManager.LayoutParams} + * @return A {@link Context} that can be used to create windows. + * @throws UnsupportedOperationException if this is called on a non-UI context, such as + * {@link android.app.Application Application} or {@link android.app.Service Service}. + * + * @see #getSystemService(String) + * @see #getSystemService(Class) + * @see #WINDOW_SERVICE + * @see #LAYOUT_INFLATER_SERVICE + * @see #WALLPAPER_SERVICE + * @throws IllegalArgumentException if token is invalid + */ + public @NonNull Context createWindowContext(int type) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * Return a new Context object for the current Context but for a different feature in the app. * Features can be used by complex apps to separate logical parts. * @@ -5813,17 +5880,22 @@ public abstract class Context { public abstract DisplayAdjustments getDisplayAdjustments(int displayId); /** + * Get the display this context is associated with. Applications should use this method with + * {@link android.app.Activity} or a context associated with a {@link Display} via + * {@link #createDisplayContext(Display)} to get a display object associated with a Context, or + * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id. * @return Returns the {@link Display} object this context is associated with. - * @hide */ - @UnsupportedAppUsage - @TestApi - public abstract Display getDisplay(); + @Nullable + public Display getDisplay() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } /** - * Gets the display ID. + * Gets the ID of the display this context is associated with. * * @return display ID associated with this {@link Context}. + * @see #getDisplay() * @hide */ @TestApi diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 6fe11873d327..b2b7988de896 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -977,6 +977,12 @@ public class ContextWrapper extends Context { } @Override + @NonNull + public Context createWindowContext(int type) { + return mBase.createWindowContext(type); + } + + @Override public @NonNull Context createFeatureContext(@Nullable String featureId) { return mBase.createFeatureContext(featureId); } @@ -992,11 +998,8 @@ public class ContextWrapper extends Context { return mBase.getDisplayAdjustments(displayId); } - /** @hide */ - @UnsupportedAppUsage - @TestApi @Override - public Display getDisplay() { + public @Nullable Display getDisplay() { return mBase.getDisplay(); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index f264adbbb592..fbcb2ddf4cad 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2029,6 +2029,9 @@ public class PackageInstaller { public boolean isCommitted; /** {@hide} */ + public long createdMillis; + + /** {@hide} */ public long updatedMillis; /** {@hide} */ @@ -2078,6 +2081,7 @@ public class PackageInstaller { mStagedSessionErrorMessage = source.readString(); isCommitted = source.readBoolean(); rollbackDataPolicy = source.readInt(); + createdMillis = source.readLong(); } /** @@ -2520,6 +2524,13 @@ public class PackageInstaller { } /** + * The timestamp of the initial creation of the session. + */ + public long getCreatedMillis() { + return createdMillis; + } + + /** * The timestamp of the last update that occurred to the session, including changing of * states in case of staged sessions. */ @@ -2568,6 +2579,7 @@ public class PackageInstaller { dest.writeString(mStagedSessionErrorMessage); dest.writeBoolean(isCommitted); dest.writeInt(rollbackDataPolicy); + dest.writeLong(createdMillis); } public static final Parcelable.Creator<SessionInfo> diff --git a/core/java/android/hardware/lights/ILightsManager.aidl b/core/java/android/hardware/lights/ILightsManager.aidl new file mode 100644 index 000000000000..6ea24b74a4a3 --- /dev/null +++ b/core/java/android/hardware/lights/ILightsManager.aidl @@ -0,0 +1,33 @@ +/** + * 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 android.hardware.lights; + +import android.hardware.lights.Light; +import android.hardware.lights.LightState; + +/** + * API to lights manager service. + * + * {@hide} + */ +interface ILightsManager { + List<Light> getLights(); + LightState getLightState(int lightId); + void openSession(in IBinder sessionToken); + void closeSession(in IBinder sessionToken); + void setLightStates(in IBinder sessionToken, in int[] lightIds, in LightState[] states); +} diff --git a/core/java/android/hardware/lights/Light.aidl b/core/java/android/hardware/lights/Light.aidl new file mode 100644 index 000000000000..946e06d44cf3 --- /dev/null +++ b/core/java/android/hardware/lights/Light.aidl @@ -0,0 +1,20 @@ +/** + * 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 android.hardware.lights; + +/** @hide */ +parcelable Light; diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java new file mode 100644 index 000000000000..c5cb8037d4db --- /dev/null +++ b/core/java/android/hardware/lights/Light.java @@ -0,0 +1,105 @@ +/** + * 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 android.hardware.lights; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a logical light on the device. + * + * @hide + */ +@SystemApi +@TestApi +public final class Light implements Parcelable { + private final int mId; + private final int mOrdinal; + private final int mType; + + /** + * Creates a new light with the given data. + * + * @hide */ + public Light(int id, int ordinal, int type) { + mId = id; + mOrdinal = ordinal; + mType = type; + } + + private Light(@NonNull Parcel in) { + mId = in.readInt(); + mOrdinal = in.readInt(); + mType = in.readInt(); + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeInt(mOrdinal); + dest.writeInt(mType); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public static final @android.annotation.NonNull Parcelable.Creator<Light> CREATOR = + new Parcelable.Creator<Light>() { + public Light createFromParcel(Parcel in) { + return new Light(in); + } + + public Light[] newArray(int size) { + return new Light[size]; + } + }; + + /** + * Returns the id of the light. + */ + public int getId() { + return mId; + } + + /** + * Returns the ordinal of the light. + * + * <p>This represents the physical order of the lights on the device. The exact values are + * device-dependent, but for example, if there are lights in a row, sorting the Light objects + * by ordinal should match the order in which they appear on the device. If the device has + * 4 lights, the ordinals could be [1, 2, 3, 4] or [0, 10, 20, 30] or any other values that + * have the same sort order. + */ + public int getOrdinal() { + return mOrdinal; + } + + /** + * Returns the logical type of the light. + */ + public @LightsManager.LightType int getType() { + return mType; + } +} diff --git a/core/java/android/hardware/lights/LightState.aidl b/core/java/android/hardware/lights/LightState.aidl new file mode 100644 index 000000000000..d598336ae40c --- /dev/null +++ b/core/java/android/hardware/lights/LightState.aidl @@ -0,0 +1,20 @@ +/** + * 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 android.hardware.lights; + +/** @hide */ +parcelable LightState; diff --git a/core/java/android/hardware/lights/LightState.java b/core/java/android/hardware/lights/LightState.java new file mode 100644 index 000000000000..e55aa702f15c --- /dev/null +++ b/core/java/android/hardware/lights/LightState.java @@ -0,0 +1,84 @@ +/** + * 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 android.hardware.lights; + +import android.annotation.ColorInt; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents the state of a device light. + * + * <p>Controlling the color and brightness of a light is done on a best-effort basis. Each of the R, + * G and B channels represent the intensities of the respective part of an RGB LED, if that is + * supported. For devices that only support on or off lights, everything that's not off will turn + * the light on. If the light is monochrome and only the brightness can be controlled, the RGB color + * will be converted to only a brightness value and that will be used for the light's single + * channel. + * + * @hide + */ +@SystemApi +@TestApi +public final class LightState implements Parcelable { + private final int mColor; + + /** + * Creates a new LightState with the desired color and intensity. + * + * @param color the desired color and intensity in ARGB format. + */ + public LightState(@ColorInt int color) { + mColor = color; + } + + private LightState(@NonNull Parcel in) { + mColor = in.readInt(); + } + + /** + * Return the color and intensity associated with this LightState. + * @return the color and intensity in ARGB format. The A channel is ignored. + */ + public @ColorInt int getColor() { + return mColor; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mColor); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Parcelable.Creator<LightState> CREATOR = + new Parcelable.Creator<LightState>() { + public LightState createFromParcel(Parcel in) { + return new LightState(in); + } + + public LightState[] newArray(int size) { + return new LightState[size]; + } + }; +} diff --git a/core/java/android/hardware/lights/LightsManager.java b/core/java/android/hardware/lights/LightsManager.java new file mode 100644 index 000000000000..1bc051b977a8 --- /dev/null +++ b/core/java/android/hardware/lights/LightsManager.java @@ -0,0 +1,204 @@ +/* + * 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 android.hardware.lights; + +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.TestApi; +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.CloseGuard; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.Reference; +import java.util.List; + +/** + * The LightsManager class allows control over device lights. + * + * @hide + */ +@SystemApi +@TestApi +@SystemService(Context.LIGHTS_SERVICE) +public final class LightsManager { + private static final String TAG = "LightsManager"; + + // These enum values copy the values from {@link com.android.server.lights.LightsManager} + // and the light HAL. Since 0-7 are lights reserved for system use, only the microphone light + // is available through this API. + /** Type for lights that indicate microphone usage */ + public static final int LIGHT_TYPE_MICROPHONE = 8; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"LIGHT_TYPE_"}, + value = { + LIGHT_TYPE_MICROPHONE, + }) + public @interface LightType {} + + @NonNull private final Context mContext; + @NonNull private final ILightsManager mService; + + /** + * Creates a LightsManager. + * + * @hide + */ + public LightsManager(@NonNull Context context) throws ServiceNotFoundException { + this(context, ILightsManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.LIGHTS_SERVICE))); + } + + /** + * Creates a LightsManager with a provided service implementation. + * + * @hide + */ + @VisibleForTesting + public LightsManager(@NonNull Context context, @NonNull ILightsManager service) { + mContext = Preconditions.checkNotNull(context); + mService = Preconditions.checkNotNull(service); + } + + /** + * Returns the lights available on the device. + * + * @return A list of available lights + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + public @NonNull List<Light> getLights() { + try { + return mService.getLights(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the state of a specified light. + * + * @hide + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + @TestApi + public @NonNull LightState getLightState(@NonNull Light light) { + Preconditions.checkNotNull(light); + try { + return mService.getLightState(light.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates a new LightsSession that can be used to control the device lights. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + public @NonNull LightsSession openSession() { + try { + final LightsSession session = new LightsSession(); + mService.openSession(session.mToken); + return session; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Encapsulates a session that can be used to control device lights and represents the lifetime + * of the requests. + */ + public final class LightsSession implements AutoCloseable { + + private final IBinder mToken = new Binder(); + + private final CloseGuard mCloseGuard = new CloseGuard(); + private boolean mClosed = false; + + /** + * Instantiated by {@link LightsManager#openSession()}. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + private LightsSession() { + mCloseGuard.open("close"); + } + + /** + * Sends a request to modify the states of multiple lights. + * + * <p>This method only controls lights that aren't overridden by higher-priority sessions. + * Additionally, lights not controlled by this session can be controlled by lower-priority + * sessions. + * + * @param request the settings for lights that should change + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + public void setLights(@NonNull LightsRequest request) { + Preconditions.checkNotNull(request); + if (!mClosed) { + try { + mService.setLightStates(mToken, request.mLightIds, request.mLightStates); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Closes the session, reverting all changes made through it. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + @Override + public void close() { + if (!mClosed) { + try { + mService.closeSession(mToken); + mClosed = true; + mCloseGuard.close(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + Reference.reachabilityFence(this); + } + + /** @hide */ + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + close(); + } finally { + super.finalize(); + } + } + } +} diff --git a/core/java/android/hardware/lights/LightsRequest.java b/core/java/android/hardware/lights/LightsRequest.java new file mode 100644 index 000000000000..a36da4c7d85d --- /dev/null +++ b/core/java/android/hardware/lights/LightsRequest.java @@ -0,0 +1,95 @@ +/* + * 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 android.hardware.lights; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.util.SparseArray; + +import com.android.internal.util.Preconditions; + +/** + * Encapsulates a request to modify the state of multiple lights. + * + * @hide + */ +@SystemApi +@TestApi +public final class LightsRequest { + + /** Visible to {@link LightsManager.Session}. */ + final int[] mLightIds; + + /** Visible to {@link LightsManager.Session}. */ + final LightState[] mLightStates; + + /** + * Can only be constructed via {@link LightsRequest.Builder#build()}. + */ + private LightsRequest(SparseArray<LightState> changes) { + final int n = changes.size(); + mLightIds = new int[n]; + mLightStates = new LightState[n]; + for (int i = 0; i < n; i++) { + mLightIds[i] = changes.keyAt(i); + mLightStates[i] = changes.valueAt(i); + } + } + + /** + * Builder for creating device light change requests. + */ + public static final class Builder { + + private final SparseArray<LightState> mChanges = new SparseArray<>(); + + /** + * Overrides the color and intensity of a given light. + * + * @param light the light to modify + * @param state the desired color and intensity of the light + */ + public @NonNull Builder setLight(@NonNull Light light, @NonNull LightState state) { + Preconditions.checkNotNull(light); + Preconditions.checkNotNull(state); + mChanges.put(light.getId(), state); + return this; + } + + /** + * Removes the override for the color and intensity of a given light. + * + * @param light the light to modify + */ + public @NonNull Builder clearLight(@NonNull Light light) { + Preconditions.checkNotNull(light); + mChanges.put(light.getId(), null); + return this; + } + + /** + * Create a LightsRequest object used to override lights on the device. + * + * <p>The generated {@link LightsRequest} should be used in + * {@link LightsManager.Session#setLights(LightsLightsRequest). + */ + public @NonNull LightsRequest build() { + return new LightsRequest(mChanges); + } + } +} diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java index 47c08a450fc4..4469d7de28fb 100644 --- a/core/java/android/net/NetworkKey.java +++ b/core/java/android/net/NetworkKey.java @@ -73,13 +73,14 @@ public class NetworkKey implements Parcelable { /** * Constructs a new NetworkKey for the given wifi {@link ScanResult}. * - * @return A new {@link NetworkKey} instance or <code>null</code> if the given - * {@link ScanResult} instance is malformed. + * @return A new {@link NetworkKey} instance or <code>null</code> if the given + * {@link ScanResult} instance is malformed. + * @throws IllegalArgumentException */ @Nullable - public static NetworkKey createFromScanResult(@Nullable ScanResult result) { + public static NetworkKey createFromScanResult(@NonNull ScanResult result) { if (result == null) { - return null; + throw new IllegalArgumentException("ScanResult cannot be null"); } final String ssid = result.SSID; if (TextUtils.isEmpty(ssid) || ssid.equals(WifiManager.UNKNOWN_SSID)) { diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index c233ec0e52cf..a190c473f0a0 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -35,6 +35,7 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -385,7 +386,6 @@ public class NetworkScoreManager { * * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public boolean requestScores(@NonNull NetworkKey[] networks) throws SecurityException { try { @@ -396,6 +396,28 @@ public class NetworkScoreManager { } /** + * Request scoring for networks. + * + * <p> + * Note: The results (i.e scores) for these networks, when available will be provided via the + * callback registered with {@link #registerNetworkScoreCallback(int, int, Executor, + * NetworkScoreCallback)}. The calling module is responsible for registering a callback to + * receive the results before requesting new scores via this API. + * + * @return true if the request was successfully sent, or false if there is no active scorer. + * @throws SecurityException if the caller does not hold the + * {@link permission#REQUEST_NETWORK_SCORES} permission. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) + public boolean requestScores(@NonNull Collection<NetworkKey> networks) + throws SecurityException { + return requestScores(networks.toArray(new NetworkKey[0])); + } + + /** * Register a network score cache. * * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}. @@ -454,26 +476,25 @@ public class NetworkScoreManager { /** * Base class for network score cache callback. Should be extended by applications and set - * when calling {@link #registerNetworkScoreCallback(int, int, NetworkScoreCallback, - * Executor)} + * when calling {@link #registerNetworkScoreCallback(int, int, Executor, NetworkScoreCallback)}. * * @hide */ @SystemApi - public interface NetworkScoreCallback { + public abstract static class NetworkScoreCallback { /** * Called when a new set of network scores are available. * This is triggered in response when the client invokes - * {@link #requestScores(NetworkKey[])} to score a new set of networks. + * {@link #requestScores(Collection)} to score a new set of networks. * * @param networks List of {@link ScoredNetwork} containing updated scores. */ - void updateScores(@NonNull List<ScoredNetwork> networks); + public abstract void onScoresUpdated(@NonNull Collection<ScoredNetwork> networks); /** * Invokes when all the previously provided scores are no longer valid. */ - void clearScores(); + public abstract void onScoresInvalidated(); } /** @@ -492,7 +513,7 @@ public class NetworkScoreManager { public void updateScores(@NonNull List<ScoredNetwork> networks) { Binder.clearCallingIdentity(); mExecutor.execute(() -> { - mCallback.updateScores(networks); + mCallback.onScoresUpdated(networks); }); } @@ -500,7 +521,7 @@ public class NetworkScoreManager { public void clearScores() { Binder.clearCallingIdentity(); mExecutor.execute(() -> { - mCallback.clearScores(); + mCallback.onScoresInvalidated(); }); } } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 904c510a5b01..0304328f734a 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -423,10 +423,14 @@ public final class Display { /** * Internal method to create a display. * The display created with this method will have a static {@link DisplayAdjustments} applied. - * Applications should use {@link android.view.WindowManager#getDefaultDisplay()} - * or {@link android.hardware.display.DisplayManager#getDisplay} - * to get a display object. + * Applications should use {@link android.content.Context#getDisplay} with + * {@link android.app.Activity} or a context associated with a {@link Display} via + * {@link android.content.Context#createDisplayContext(Display)} + * to get a display object associated with a {@link android.app.Context}, or + * {@link android.hardware.display.DisplayManager#getDisplay} to get a display object by id. * + * @see android.content.Context#getDisplay() + * @see android.content.Context#createDisplayContext(Display) * @hide */ public Display(DisplayManagerGlobal global, int displayId, /*@NotNull*/ DisplayInfo displayInfo, diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 993bdc4d6543..5eb319f19cce 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -111,6 +111,20 @@ interface IWindowManager // These can only be called when holding the MANAGE_APP_TOKENS permission. void setEventDispatching(boolean enabled); + + /** @return {@code true} if this binder is a registered window token. */ + boolean isWindowToken(in IBinder binder); + /** + * Adds window token for a given type. + * + * @param token Token to be registered. + * @param type Window type to be used with this token. + * @param displayId The ID of the display where this token should be added. + * @param packageName The name of package to request to add window token. + * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code + * otherwise. + */ + int addWindowContextToken(IBinder token, int type, int displayId, String packageName); void addWindowToken(IBinder token, int type, int displayId); void removeWindowToken(IBinder token, int displayId); void prepareAppTransition(int transit, boolean alwaysKeepCurrent); diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 271566acb74e..8d58ee83cd67 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -45,12 +45,25 @@ public final class ImeFocusController { mViewRootImpl = viewRootImpl; } + @NonNull private InputMethodManagerDelegate getImmDelegate() { - if (mDelegate == null) { - mDelegate = mViewRootImpl.mContext.getSystemService( - InputMethodManager.class).getDelegate(); + InputMethodManagerDelegate delegate = mDelegate; + if (delegate != null) { + return delegate; } - return mDelegate; + delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate(); + mDelegate = delegate; + return delegate; + } + + /** Called when the view root is moved to a different display. */ + @UiThread + void onMovedToDisplay() { + // InputMethodManager managed its instances for different displays. So if the associated + // display is changed, the delegate also needs to be refreshed (by getImmDelegate). + // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager + // and {@link android.view.inputmethod.InputMethodManager#forContext}. + mDelegate = null; } @UiThread @@ -103,7 +116,8 @@ public final class ImeFocusController { } boolean forceFocus = false; - if (getImmDelegate().isRestartOnNextWindowFocus(true /* reset */)) { + final InputMethodManagerDelegate immDelegate = getImmDelegate(); + if (immDelegate.isRestartOnNextWindowFocus(true /* reset */)) { if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true"); forceFocus = true; } @@ -111,12 +125,13 @@ public final class ImeFocusController { final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; onViewFocusChanged(viewForWindowFocus, true); - getImmDelegate().startInputAsyncOnWindowFocusGain(viewForWindowFocus, + immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } public boolean checkFocus(boolean forceNewFocus, boolean startInput) { - if (!getImmDelegate().isCurrentRootView(mViewRootImpl) + final InputMethodManagerDelegate immDelegate = getImmDelegate(); + if (!immDelegate.isCurrentRootView(mViewRootImpl) || (mServedView == mNextServedView && !forceNewFocus)) { return false; } @@ -128,15 +143,16 @@ public final class ImeFocusController { // Close the connection when no next served view coming. if (mNextServedView == null) { - getImmDelegate().finishInput(); - getImmDelegate().closeCurrentIme(); + immDelegate.finishInput(); + immDelegate.closeCurrentIme(); return false; } mServedView = mNextServedView; - getImmDelegate().finishComposingText(); + immDelegate.finishComposingText(); if (startInput) { - getImmDelegate().startInput(StartInputReason.CHECK_FOCUS, null, 0, 0, 0); + immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, + 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); } return true; } @@ -169,13 +185,14 @@ public final class ImeFocusController { @UiThread void onWindowDismissed() { - if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) { + final InputMethodManagerDelegate immDelegate = getImmDelegate(); + if (!immDelegate.isCurrentRootView(mViewRootImpl)) { return; } if (mServedView != null) { - getImmDelegate().finishInput(); + immDelegate.finishInput(); } - getImmDelegate().setCurrentRootView(null); + immDelegate.setCurrentRootView(null); mHasImeFocus = false; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2d9009fea99b..f5cfbec924ac 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1448,6 +1448,7 @@ public final class ViewRootImpl implements ViewParent, // Get new instance of display based on current display adjustments. It may be updated later // if moving between the displays also involved a configuration change. updateInternalDisplay(displayId, mView.getResources()); + mImeFocusController.onMovedToDisplay(); mAttachInfo.mDisplayState = mDisplay.getState(); // Internal state updated, now notify the view hierarchy. mView.dispatchMovedToDisplay(mDisplay, config); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 560ea3bc8929..1333ffe272d3 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -421,7 +421,9 @@ public interface WindowManager extends ViewManager { * </p> * * @return The display that this window manager is managing. + * @deprecated Use {@link Context#getDisplay()} instead. */ + @Deprecated public Display getDefaultDisplay(); /** diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 08022e983892..b2aa0431251d 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -26,7 +26,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.PagerAdapter; import com.android.internal.widget.ViewPager; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; /** * Skeletal {@link PagerAdapter} implementation of a work or personal profile page for @@ -34,6 +36,7 @@ import java.util.Objects; */ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { + private static final String TAG = "AbstractMultiProfilePagerAdapter"; static final int PROFILE_PERSONAL = 0; static final int PROFILE_WORK = 1; @IntDef({PROFILE_PERSONAL, PROFILE_WORK}) @@ -41,10 +44,17 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private final Context mContext; private int mCurrentPage; + private OnProfileSelectedListener mOnProfileSelectedListener; + private Set<Integer> mLoadedPages; AbstractMultiProfilePagerAdapter(Context context, int currentPage) { mContext = Objects.requireNonNull(context); mCurrentPage = currentPage; + mLoadedPages = new HashSet<>(); + } + + void setOnProfileSelectedListener(OnProfileSelectedListener listener) { + mOnProfileSelectedListener = listener; } Context getContext() { @@ -57,15 +67,22 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * page and rebuilds the list. */ void setupViewPager(ViewPager viewPager) { - viewPager.setCurrentItem(mCurrentPage); viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { mCurrentPage = position; - getActiveListAdapter().rebuildList(); + if (!mLoadedPages.contains(position)) { + getActiveListAdapter().rebuildList(); + mLoadedPages.add(position); + } + if (mOnProfileSelectedListener != null) { + mOnProfileSelectedListener.onProfileSelected(position); + } } }); viewPager.setAdapter(this); + viewPager.setCurrentItem(mCurrentPage); + mLoadedPages.add(mCurrentPage); } @Override @@ -90,7 +107,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { return mCurrentPage; } - UserHandle getCurrentUserHandle() { + @VisibleForTesting + public UserHandle getCurrentUserHandle() { return getActiveListAdapter().mResolverListController.getUserHandle(); } @@ -135,7 +153,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * <p>This method is meant to be implemented with an implementation-specific return type * depending on the adapter type. */ - abstract Object getAdapterForIndex(int pageIndex); + @VisibleForTesting + public abstract Object getAdapterForIndex(int pageIndex); @VisibleForTesting public abstract ResolverListAdapter getActiveListAdapter(); @@ -152,7 +171,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { abstract Object getCurrentRootAdapter(); - abstract ViewGroup getCurrentAdapterView(); + abstract ViewGroup getActiveAdapterView(); + + abstract @Nullable ViewGroup getInactiveAdapterView(); protected class ProfileDescriptor { final ViewGroup rootView; @@ -160,4 +181,15 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { this.rootView = rootView; } } + + public interface OnProfileSelectedListener { + /** + * Callback for when the user changes the active tab from personal to work or vice versa. + * <p>This callback is only called when the intent resolver or share sheet shows + * the work and personal profiles. + * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or + * {@link #PROFILE_WORK} if the work profile was selected. + */ + void onProfileSelected(int profileIndex); + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 9532faecb4df..8bbc343fa4ca 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -508,7 +508,6 @@ public class ChooserActivity extends ResolverActivity implements protected void onCreate(Bundle savedInstanceState) { final long intentReceivedTime = System.currentTimeMillis(); // This is the only place this value is being set. Effectively final. - //TODO(arangelov) - should there be a mIsAppPredictorComponentAvailable flag for work tab? mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable(); mIsSuccessfullySelected = false; @@ -689,29 +688,6 @@ public class ChooserActivity extends ResolverActivity implements mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll); } - final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header); - final float defaultElevation = chooserHeader.getElevation(); - final float chooserHeaderScrollElevation = - getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation); - - mChooserMultiProfilePagerAdapter.getCurrentAdapterView().addOnScrollListener( - new RecyclerView.OnScrollListener() { - public void onScrollStateChanged(RecyclerView view, int scrollState) { - } - - public void onScrolled(RecyclerView view, int dx, int dy) { - if (view.getChildCount() > 0) { - View child = view.getLayoutManager().findViewByPosition(0); - if (child == null || child.getTop() < 0) { - chooserHeader.setElevation(chooserHeaderScrollElevation); - return; - } - } - - chooserHeader.setElevation(defaultElevation); - } - }); - mResolverDrawerLayout.setOnCollapsedChangedListener( new ResolverDrawerLayout.OnCollapsedChangedListener() { @@ -1330,8 +1306,8 @@ public class ChooserActivity extends ResolverActivity implements } @Override - public void onPrepareAdapterView(ResolverListAdapter adapter) { - mChooserMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE); + public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) { + mChooserMultiProfilePagerAdapter.getActiveAdapterView().setVisibility(View.VISIBLE); if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) { mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( /* origTarget */ null, @@ -2202,7 +2178,7 @@ public class ChooserActivity extends ResolverActivity implements if (mChooserMultiProfilePagerAdapter == null) { return; } - RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getCurrentAdapterView(); + RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView(); ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter(); if (gridAdapter == null || recyclerView == null) { return; @@ -2328,6 +2304,8 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onListRebuilt(ResolverListAdapter listAdapter) { + setupScrollListener(); + ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter; if (chooserListAdapter.mDisplayList == null || chooserListAdapter.mDisplayList.isEmpty()) { @@ -2368,6 +2346,34 @@ public class ChooserActivity extends ResolverActivity implements } } + private void setupScrollListener() { + if (mResolverDrawerLayout == null) { + return; + } + final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header); + final float defaultElevation = chooserHeader.getElevation(); + final float chooserHeaderScrollElevation = + getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation); + + mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener( + new RecyclerView.OnScrollListener() { + public void onScrollStateChanged(RecyclerView view, int scrollState) { + } + + public void onScrolled(RecyclerView view, int dx, int dy) { + if (view.getChildCount() > 0) { + View child = view.getLayoutManager().findViewByPosition(0); + if (child == null || child.getTop() < 0) { + chooserHeader.setElevation(chooserHeaderScrollElevation); + return; + } + } + + chooserHeader.setElevation(defaultElevation); + } + }); + } + @Override // ChooserListCommunicator public boolean isSendAction(Intent targetIntent) { if (targetIntent == null) { @@ -2475,7 +2481,8 @@ public class ChooserActivity extends ResolverActivity implements * row level by this adapter but not on the item level. Individual targets within the row are * handled by {@link ChooserListAdapter} */ - final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { + @VisibleForTesting + public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private ChooserListAdapter mChooserListAdapter; private final LayoutInflater mLayoutInflater; @@ -2905,7 +2912,7 @@ public class ChooserActivity extends ResolverActivity implements if (mDirectShareViewHolder != null && canExpandDirectShare) { mDirectShareViewHolder.handleScroll( - mChooserMultiProfilePagerAdapter.getCurrentAdapterView(), y, oldy, + mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy, getMaxTargetsPerRow()); } } diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index a8a676d03971..6ff844a2eaae 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -246,11 +246,6 @@ public class ChooserListAdapter extends ResolverListAdapter { } @Override - public boolean shouldGetResolvedFilter() { - return true; - } - - @Override public int getCount() { return getRankedTargetCount() + getAlphaTargetCount() + getSelectableServiceTargetCount() + getCallerTargetCount(); diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 7d856e1b945d..1c52d0e8f9f1 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -16,6 +16,7 @@ package com.android.internal.app; +import android.annotation.Nullable; import android.content.Context; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -77,7 +78,8 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd } @Override - ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) { + @VisibleForTesting + public ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) { return mItems[pageIndex].chooserGridAdapter; } @@ -121,10 +123,19 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd } @Override - RecyclerView getCurrentAdapterView() { + RecyclerView getActiveAdapterView() { return getListViewForIndex(getCurrentPage()); } + @Override + @Nullable + RecyclerView getInactiveAdapterView() { + if (getCount() == 1) { + return null; + } + return getListViewForIndex(1 - getCurrentPage()); + } + class ChooserProfileDescriptor extends ProfileDescriptor { private ChooserActivity.ChooserGridAdapter chooserGridAdapter; private RecyclerView recyclerView; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index b2417b2e79cc..68d6e03f654c 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -59,6 +59,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -67,9 +68,12 @@ import android.view.WindowInsets; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.Space; +import android.widget.TabHost; +import android.widget.TabWidget; import android.widget.TextView; import android.widget.Toast; @@ -82,6 +86,7 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.widget.ResolverDrawerLayout; +import com.android.internal.widget.ViewPager; import java.util.ArrayList; import java.util.Arrays; @@ -147,7 +152,10 @@ public class ResolverActivity extends Activity implements /** * TODO(arangelov): Remove a couple of weeks after work/personal tabs are finalized. */ - static final boolean ENABLE_TABBED_VIEW = false; + @VisibleForTesting + public static boolean ENABLE_TABBED_VIEW = false; + private static final String TAB_TAG_PERSONAL = "personal"; + private static final String TAB_TAG_WORK = "work"; private final PackageMonitor mPackageMonitor = createPackageMonitor(); @@ -418,12 +426,16 @@ public class ResolverActivity extends Activity implements Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) { + // We only show the default app for the profile of the current user. The filterLastUsed + // flag determines whether to show a default app and that app is not shown in the + // resolver list. So filterLastUsed should be false for the other profile. ResolverListAdapter personalAdapter = createResolverListAdapter( /* context */ this, /* payloadIntents */ mIntents, initialIntents, rList, - filterLastUsed, + (filterLastUsed && UserHandle.myUserId() + == getPersonalProfileUserHandle().getIdentifier()), mUseLayoutForBrowsables, /* userHandle */ getPersonalProfileUserHandle()); ResolverListAdapter workAdapter = createResolverListAdapter( @@ -431,7 +443,8 @@ public class ResolverActivity extends Activity implements /* payloadIntents */ mIntents, initialIntents, rList, - filterLastUsed, + (filterLastUsed && UserHandle.myUserId() + == getWorkProfileUserHandle().getIdentifier()), mUseLayoutForBrowsables, /* userHandle */ getWorkProfileUserHandle()); return new ResolverMultiProfilePagerAdapter( @@ -495,12 +508,12 @@ public class ResolverActivity extends Activity implements mFooterSpacer = new Space(getApplicationContext()); } else { ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) - .getCurrentAdapterView().removeFooterView(mFooterSpacer); + .getActiveAdapterView().removeFooterView(mFooterSpacer); } mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, mSystemWindowInsets.bottom)); ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) - .getCurrentAdapterView().addFooterView(mFooterSpacer); + .getActiveAdapterView().addFooterView(mFooterSpacer); } protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { @@ -817,7 +830,7 @@ public class ResolverActivity extends Activity implements public void onButtonClick(View v) { final int id = v.getId(); - ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView(); + ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); int which = currentListAdapter.hasFilteredItem() ? currentListAdapter.getFilteredPosition() @@ -898,7 +911,10 @@ public class ResolverActivity extends Activity implements @Override // ResolverListCommunicator public void onPostListReady(ResolverListAdapter listAdapter) { - setHeader(); + if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() + == UserHandle.myUserId()) { + setHeader(); + } resetButtonBar(); onListRebuilt(listAdapter); } @@ -913,6 +929,9 @@ public class ResolverActivity extends Activity implements finish(); } } + + final ItemClickListener listener = new ItemClickListener(); + setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener); } protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { @@ -1094,6 +1113,7 @@ public class ResolverActivity extends Activity implements return true; } + @VisibleForTesting public void safelyStartActivity(TargetInfo cti) { // We're dispatching intents that might be coming from legacy apps, so // don't kill ourselves. @@ -1222,9 +1242,6 @@ public class ResolverActivity extends Activity implements + "cannot be null."); } boolean rebuildCompleted = mMultiProfilePagerAdapter.getActiveListAdapter().rebuildList(); - if (mMultiProfilePagerAdapter.getInactiveListAdapter() != null) { - mMultiProfilePagerAdapter.getInactiveListAdapter().rebuildList(); - } if (useLayoutWithDefault()) { mLayoutId = R.layout.resolver_list_with_default; } else { @@ -1272,45 +1289,99 @@ public class ResolverActivity extends Activity implements } } - setupViewVisibilities(count); + setupViewVisibilities(); + + if (hasWorkProfile() && ENABLE_TABBED_VIEW) { + setupProfileTabs(); + } + return false; } - private void setupViewVisibilities(int count) { - if (count == 0 - && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) { - final TextView emptyView = findViewById(R.id.empty); - emptyView.setVisibility(View.VISIBLE); - findViewById(R.id.profile_pager).setVisibility(View.GONE); - } else { - onPrepareAdapterView(mMultiProfilePagerAdapter.getActiveListAdapter()); + private void setupProfileTabs() { + TabHost tabHost = findViewById(R.id.profile_tabhost); + tabHost.setup(); + ViewPager viewPager = findViewById(R.id.profile_pager); + TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL) + .setContent(R.id.profile_pager) + .setIndicator(getString(R.string.resolver_personal_tab)); + tabHost.addTab(tabSpec); + + tabSpec = tabHost.newTabSpec(TAB_TAG_WORK) + .setContent(R.id.profile_pager) + .setIndicator(getString(R.string.resolver_work_tab)); + tabHost.addTab(tabSpec); + + TabWidget tabWidget = tabHost.getTabWidget(); + tabWidget.setVisibility(View.VISIBLE); + resetTabsHeaderStyle(tabWidget); + updateActiveTabStyle(tabHost); + + tabHost.setOnTabChangedListener(tabId -> { + resetTabsHeaderStyle(tabWidget); + updateActiveTabStyle(tabHost); + if (TAB_TAG_PERSONAL.equals(tabId)) { + viewPager.setCurrentItem(0); + } else { + viewPager.setCurrentItem(1); + } + setupViewVisibilities(); + }); + + viewPager.setVisibility(View.VISIBLE); + tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage()); + mMultiProfilePagerAdapter.setOnProfileSelectedListener(tabHost::setCurrentTab); + } + + private void resetTabsHeaderStyle(TabWidget tabWidget) { + for (int i = 0; i < tabWidget.getChildCount(); i++) { + TextView title = tabWidget.getChildAt(i).findViewById(android.R.id.title); + title.setTextColor(getColor(R.color.resolver_tabs_inactive_color)); + title.setAllCaps(false); + } + } + + private void updateActiveTabStyle(TabHost tabHost) { + TextView title = tabHost.getTabWidget().getChildAt(tabHost.getCurrentTab()) + .findViewById(android.R.id.title); + title.setTextColor(getColor(R.color.resolver_tabs_active_color)); + } + + private void setupViewVisibilities() { + int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); + boolean shouldShowEmptyState = count == 0 + && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0; + //TODO(arangelov): Handle empty state + if (!shouldShowEmptyState) { + addUseDifferentAppLabelIfNecessary(mMultiProfilePagerAdapter.getActiveListAdapter()); } } /** - * Prepare the scrollable view which consumes data in the list adapter. + * Add a label to signify that the user can pick a different app. * @param adapter The adapter used to provide data to item views. */ - public void onPrepareAdapterView(ResolverListAdapter adapter) { - mMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE); + public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) { final boolean useHeader = adapter.hasFilteredItem(); - final ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView(); - final ItemClickListener listener = new ItemClickListener(); + if (useHeader) { + FrameLayout stub = findViewById(R.id.stub); + stub.setVisibility(View.VISIBLE); + TextView textView = (TextView) LayoutInflater.from(this).inflate( + R.layout.resolver_different_item_header, null, false); + if (ENABLE_TABBED_VIEW) { + textView.setGravity(Gravity.CENTER); + } + stub.addView(textView); + } + } + + private void setupAdapterListView(ListView listView, ItemClickListener listener) { listView.setOnItemClickListener(listener); listView.setOnItemLongClickListener(listener); if (mSupportsAlwaysUseOption || mUseLayoutForBrowsables) { listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); } - - // In case this method is called again (due to activity recreation), avoid adding a new - // header if one is already present. - if (useHeader && listView.getHeaderViewsCount() == 0) { - listView.setHeaderDividersEnabled(true); - listView.addHeaderView(LayoutInflater.from(this).inflate( - R.layout.resolver_different_item_header, listView, false), - null, false); - } } /** @@ -1378,7 +1449,7 @@ public class ResolverActivity extends Activity implements } // When the items load in, if an item was already selected, enable the buttons - ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView(); + ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); if (currentAdapterView != null && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) { setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true); @@ -1388,8 +1459,18 @@ public class ResolverActivity extends Activity implements @Override // ResolverListCommunicator public boolean useLayoutWithDefault() { - return mSupportsAlwaysUseOption - && mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem(); + // We only use the default app layout when the profile of the active user has a + // filtered item. We always show the same default app even in the inactive user profile. + boolean currentUserAdapterHasFilteredItem; + if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() + == UserHandle.myUserId()) { + currentUserAdapterHasFilteredItem = + mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem(); + } else { + currentUserAdapterHasFilteredItem = + mMultiProfilePagerAdapter.getInactiveListAdapter().hasFilteredItem(); + } + return mSupportsAlwaysUseOption && currentUserAdapterHasFilteredItem; } /** @@ -1494,9 +1575,8 @@ public class ResolverActivity extends Activity implements .resolveInfoForPosition(position, true) == null) { return; } - ListView currentAdapterView = - (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView(); + (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); final int checkedPos = currentAdapterView.getCheckedItemPosition(); final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; if (!useLayoutWithDefault() diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index ef7a347cf7be..d227ec5c1b38 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -193,7 +193,8 @@ public class ResolverListAdapter extends BaseAdapter { mBaseResolveList); } else { currentResolveList = mUnfilteredResolveList = - mResolverListController.getResolversForIntent(shouldGetResolvedFilter(), + mResolverListController.getResolversForIntent( + /* shouldGetResolvedFilter= */ true, mResolverListCommunicator.shouldGetActivityMetadata(), mIntents); if (currentResolveList == null) { @@ -363,10 +364,6 @@ public class ResolverListAdapter extends BaseAdapter { } } - public boolean shouldGetResolvedFilter() { - return mFilterLastUsed; - } - private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) { final int count = rci.getCount(); final Intent intent = rci.getIntentAt(0); diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index abd3eb2453df..0bfe9eb04d28 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -133,7 +133,8 @@ public class ResolverListController { return resolvedComponents; } - UserHandle getUserHandle() { + @VisibleForTesting + public UserHandle getUserHandle() { return mUserHandle; } diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index d72c52bfe6b6..567ed74670bf 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -16,6 +16,7 @@ package com.android.internal.app; +import android.annotation.Nullable; import android.content.Context; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -81,7 +82,8 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA } @Override - ResolverListAdapter getAdapterForIndex(int pageIndex) { + @VisibleForTesting + public ResolverListAdapter getAdapterForIndex(int pageIndex) { return mItems[pageIndex].resolverListAdapter; } @@ -106,10 +108,19 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA } @Override - ListView getCurrentAdapterView() { + ListView getActiveAdapterView() { return getListViewForIndex(getCurrentPage()); } + @Override + @Nullable + ViewGroup getInactiveAdapterView() { + if (getCount() == 1) { + return null; + } + return getListViewForIndex(1 - getCurrentPage()); + } + class ResolverProfileDescriptor extends ProfileDescriptor { private ResolverListAdapter resolverListAdapter; final ListView listView; diff --git a/core/java/com/android/internal/app/WrapHeightViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java index b017bb44d751..8ec94f159725 100644 --- a/core/java/com/android/internal/app/WrapHeightViewPager.java +++ b/core/java/com/android/internal/app/ResolverViewPager.java @@ -18,39 +18,36 @@ package com.android.internal.app; import android.content.Context; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import com.android.internal.widget.ViewPager; /** - * A {@link ViewPager} which wraps around its first child's height. + * A {@link ViewPager} which wraps around its first child's height and has swiping disabled. * <p>Normally {@link ViewPager} instances expand their height to cover all remaining space in * the layout. - * <p>This class is used for the intent resolver picker's tabbed view to maintain - * consistency with the previous behavior. + * <p>This class is used for the intent resolver and share sheet's tabbed view. */ -public class WrapHeightViewPager extends ViewPager { +public class ResolverViewPager extends ViewPager { - public WrapHeightViewPager(Context context) { + public ResolverViewPager(Context context) { super(context); } - public WrapHeightViewPager(Context context, AttributeSet attrs) { + public ResolverViewPager(Context context, AttributeSet attrs) { super(context, attrs); } - public WrapHeightViewPager(Context context, AttributeSet attrs, int defStyleAttr) { + public ResolverViewPager(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } - public WrapHeightViewPager(Context context, AttributeSet attrs, + public ResolverViewPager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } - // TODO(arangelov): When we have multiple pages, the height should wrap to the currently - // displayed page. Investigate whether onMeasure is called when changing a page, and instead - // of getChildAt(0), use the currently displayed one. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -68,4 +65,14 @@ public class WrapHeightViewPager extends ViewPager { heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return false; + } } diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index e266fe623947..0156e23e94b6 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -307,6 +307,43 @@ static int _check_AudioSystem_Command(const char* caller, status_t status) return kAudioStatusError; } +static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes, + jobjectArray deviceAddresses, + Vector<AudioDeviceTypeAddr> &audioDeviceTypeAddrVector) { + if (deviceTypes == nullptr || deviceAddresses == nullptr) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jsize deviceCount = env->GetArrayLength(deviceTypes); + if (deviceCount == 0 || deviceCount != env->GetArrayLength(deviceAddresses)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + // retrieve all device types + std::vector<audio_devices_t> deviceTypesVector; + jint *typesPtr = nullptr; + typesPtr = env->GetIntArrayElements(deviceTypes, 0); + if (typesPtr == nullptr) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + for (jint i = 0; i < deviceCount; i++) { + deviceTypesVector.push_back((audio_devices_t)typesPtr[i]); + } + // check each address is a string and add device type/address to list + jclass stringClass = FindClassOrDie(env, "java/lang/String"); + for (jint i = 0; i < deviceCount; i++) { + jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i); + if (!env->IsInstanceOf(addrJobj, stringClass)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL); + AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address); + audioDeviceTypeAddrVector.add(dev); + env->ReleaseStringUTFChars((jstring)addrJobj, address); + } + env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0); + + return (jint)NO_ERROR; +} + static jint android_media_AudioSystem_muteMicrophone(JNIEnv *env, jobject thiz, jboolean on) { @@ -1905,6 +1942,10 @@ static jint convertAudioMixToNative(JNIEnv *env, nCriterion.mValue.mUid = env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp); break; + case RULE_MATCH_USERID: + nCriterion.mValue.mUserId = + env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp); + break; case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: { jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr); @@ -1990,39 +2031,11 @@ exit: static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz, jint uid, jintArray deviceTypes, jobjectArray deviceAddresses) { - if (deviceTypes == nullptr || deviceAddresses == nullptr) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - jsize nb = env->GetArrayLength(deviceTypes); - if (nb == 0 || nb != env->GetArrayLength(deviceAddresses)) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - // retrieve all device types - std::vector<audio_devices_t> deviceTypesVector; - jint* typesPtr = nullptr; - typesPtr = env->GetIntArrayElements(deviceTypes, 0); - if (typesPtr == nullptr) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - for (jint i = 0; i < nb; i++) { - deviceTypesVector.push_back((audio_devices_t) typesPtr[i]); - } - - // check each address is a string and add device type/address to list for device affinity Vector<AudioDeviceTypeAddr> deviceVector; - jclass stringClass = FindClassOrDie(env, "java/lang/String"); - for (jint i = 0; i < nb; i++) { - jobject addrJobj = env->GetObjectArrayElement(deviceAddresses, i); - if (!env->IsInstanceOf(addrJobj, stringClass)) { - return (jint) AUDIO_JAVA_BAD_VALUE; - } - const char* address = env->GetStringUTFChars((jstring) addrJobj, NULL); - AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address); - deviceVector.add(dev); - env->ReleaseStringUTFChars((jstring) addrJobj, address); + jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector); + if (results != NO_ERROR) { + return results; } - env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0); - status_t status = AudioSystem::setUidDeviceAffinities((uid_t) uid, deviceVector); return (jint) nativeToJavaStatus(status); } @@ -2033,6 +2046,23 @@ static jint android_media_AudioSystem_removeUidDeviceAffinities(JNIEnv *env, job return (jint) nativeToJavaStatus(status); } +static jint android_media_AudioSystem_setUserIdDeviceAffinities(JNIEnv *env, jobject clazz, + jint userId, jintArray deviceTypes, + jobjectArray deviceAddresses) { + Vector<AudioDeviceTypeAddr> deviceVector; + jint results = getVectorOfAudioDeviceTypeAddr(env, deviceTypes, deviceAddresses, deviceVector); + if (results != NO_ERROR) { + return results; + } + status_t status = AudioSystem::setUserIdDeviceAffinities((int)userId, deviceVector); + return (jint)nativeToJavaStatus(status); +} + +static jint android_media_AudioSystem_removeUserIdDeviceAffinities(JNIEnv *env, jobject clazz, + jint userId) { + status_t status = AudioSystem::removeUserIdDeviceAffinities((int)userId); + return (jint)nativeToJavaStatus(status); +} static jint android_media_AudioSystem_systemReady(JNIEnv *env, jobject thiz) @@ -2463,7 +2493,9 @@ static const JNINativeMethod gMethods[] = { {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy}, {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy}, {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy}, - {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes} + {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}, + {"setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I", (void *)android_media_AudioSystem_setUserIdDeviceAffinities}, + {"removeUserIdDeviceAffinities", "(I)I", (void *)android_media_AudioSystem_removeUserIdDeviceAffinities} }; static const JNINativeMethod gEventHandlerMethods[] = { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1fb08f260393..4595bab82465 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1616,6 +1616,7 @@ <!-- Allows applications to request network recommendations and scores from the NetworkScoreService. + @SystemApi <p>Not for use by third-party applications. @hide --> <permission android:name="android.permission.REQUEST_NETWORK_SCORES" android:protectionLevel="signature|setup" /> @@ -2534,6 +2535,14 @@ android:description="@string/permdesc_useDataInBackground" android:protectionLevel="normal" /> + <!-- Allows a companion app to associate to Wi-Fi. + <p>Only for use by a single pre-approved app. + @hide + @SystemApi + --> + <permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" + android:protectionLevel="signature|privileged" /> + <!-- ================================== --> <!-- Permissions affecting the system wallpaper --> @@ -3723,6 +3732,13 @@ <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE" android:protectionLevel="signature" /> + <!-- Allows an application to control the lights on the device. + @hide + @SystemApi + @TestApi --> + <permission android:name="android.permission.CONTROL_DEVICE_LIGHTS" + android:protectionLevel="signature|privileged" /> + <!-- Allows an application to control the color saturation of the display. @hide @SystemApi --> diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index 0c45e45e7980..4e8a41f81c48 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -61,10 +61,33 @@ android:layout_height="wrap_content" android:visibility="gone" /> - <com.android.internal.widget.ViewPager - android:id="@+id/profile_pager" + <TabHost + android:id="@+id/profile_tabhost" android:layout_width="match_parent" - android:layout_height="wrap_content"/> + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:background="?attr/colorBackgroundFloating"> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TabWidget + android:id="@android:id/tabs" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + </TabWidget> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.internal.app.ResolverViewPager + android:id="@+id/profile_pager" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </FrameLayout> + </LinearLayout> + </TabHost> <TextView android:id="@+id/empty" android:layout_width="match_parent" diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml index c5d891254227..757cd539152d 100644 --- a/core/res/res/layout/resolver_list.xml +++ b/core/res/res/layout/resolver_list.xml @@ -70,14 +70,44 @@ android:background="?attr/colorBackgroundFloating" android:foreground="?attr/dividerVertical" /> - <com.android.internal.app.WrapHeightViewPager - android:id="@+id/profile_pager" + <FrameLayout + android:id="@+id/stub" + android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" - android:divider="?attr/dividerVertical" - android:footerDividersEnabled="false" - android:headerDividersEnabled="false" - android:dividerHeight="1dp"/> + android:background="?attr/colorBackgroundFloating"/> + + <TabHost + android:id="@+id/profile_tabhost" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:background="?attr/colorBackgroundFloating"> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TabWidget + android:id="@android:id/tabs" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + </TabWidget> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.internal.app.ResolverViewPager + android:id="@+id/profile_pager" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:divider="?attr/dividerVertical" + android:footerDividersEnabled="false" + android:headerDividersEnabled="false" + android:dividerHeight="1dp"/> + </FrameLayout> + </LinearLayout> + </TabHost> <View android:layout_alwaysShow="true" diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml index 68b991755e73..6d8d3480dc8c 100644 --- a/core/res/res/layout/resolver_list_per_profile.xml +++ b/core/res/res/layout/resolver_list_per_profile.xml @@ -25,7 +25,7 @@ android:nestedScrollingEnabled="true" android:scrollbarStyle="outsideOverlay" android:scrollIndicators="top|bottom" - android:divider="?attr/dividerVertical" + android:divider="@null" android:footerDividersEnabled="false" android:headerDividersEnabled="false" - android:dividerHeight="1dp" />
\ No newline at end of file + android:dividerHeight="0dp" />
\ No newline at end of file diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml index 5b3d929d23a5..b54673834ea9 100644 --- a/core/res/res/layout/resolver_list_with_default.xml +++ b/core/res/res/layout/resolver_list_with_default.xml @@ -151,14 +151,46 @@ android:background="?attr/colorBackgroundFloating" android:foreground="?attr/dividerVertical" /> - <com.android.internal.app.WrapHeightViewPager - android:id="@+id/profile_pager" + <FrameLayout + android:id="@+id/stub" + android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" - android:dividerHeight="1dp" - android:divider="?attr/dividerVertical" - android:footerDividersEnabled="false" - android:headerDividersEnabled="false"/> + android:background="?attr/colorBackgroundFloating"/> + + <TabHost + android:id="@+id/profile_tabhost" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:background="?attr/colorBackgroundFloating"> + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TabWidget + android:id="@android:id/tabs" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone"> + </TabWidget> + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.internal.app.ResolverViewPager + android:id="@+id/profile_pager" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:dividerHeight="1dp" + android:divider="?attr/dividerVertical" + android:footerDividersEnabled="false" + android:headerDividersEnabled="false"/> + </FrameLayout> + </LinearLayout> + </TabHost> + <View android:layout_alwaysShow="true" android:layout_width="match_parent" diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 1dcd389d9d8f..731e2ec95f9e 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -224,4 +224,6 @@ <!-- Resolver/Chooser --> <color name="resolver_text_color_secondary_dark">#ffC4C6C6</color> + <color name="resolver_tabs_active_color">#FF1A73E8</color> + <color name="resolver_tabs_inactive_color">#FF80868B</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ec3208e5b737..42127e77317f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4288,6 +4288,13 @@ generation). --> <bool name="config_customBugreport">false</bool> + <!-- Names of packages that should not be suspended when personal use is blocked by policy. --> + <string-array name="config_packagesExemptFromSuspension" translatable="false"> + <!-- Add packages here, example: --> + <!-- <item>com.android.settings</item> --> + </string-array> + + <!-- Class name of the custom country detector to be used. --> <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index bed418dc4c2d..be2b678565d3 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -425,6 +425,12 @@ <!-- A toast message displayed when printing is attempted but disabled by policy. --> <string name="printing_disabled_by">Printing disabled by <xliff:g id="owner_app">%s</xliff:g>.</string> + <!-- Content title for a notification that personal apps are suspended [CHAR LIMIT=NONE] --> + <string name="personal_apps_suspended_notification_title">Personal apps have been suspended by an admin</string> + + <!-- Message for a notification about personal apps suspension when work profile is off. [CHAR LIMIT=NONE] --> + <string name="personal_apps_suspended_notification_text">Tap here to check policy compliance.</string> + <!-- Display name for any time a piece of data refers to the owner of the phone. For example, this could be used in place of the phone's phone number. --> <string name="me">Me</string> @@ -5319,4 +5325,8 @@ <!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] --> <string name="as_app_forced_to_restricted_bucket"> <xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string> + + <!-- ResolverActivity - profile tabs --> + <string name="resolver_personal_tab">Personal</string> + <string name="resolver_work_tab">Work</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 81ef872e3f0a..ea8ca9aa1c49 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -249,6 +249,9 @@ <java-symbol type="id" name="app_ops" /> <java-symbol type="id" name="profile_pager" /> <java-symbol type="id" name="content_preview_container" /> + <java-symbol type="id" name="profile_tabhost" /> + <java-symbol type="id" name="tabs" /> + <java-symbol type="id" name="tabcontent" /> <java-symbol type="attr" name="actionModeShareDrawable" /> <java-symbol type="attr" name="alertDialogCenterButtons" /> @@ -1209,6 +1212,8 @@ <java-symbol type="string" name="device_ownership_relinquished" /> <java-symbol type="string" name="network_logging_notification_title" /> <java-symbol type="string" name="network_logging_notification_text" /> + <java-symbol type="string" name="personal_apps_suspended_notification_title" /> + <java-symbol type="string" name="personal_apps_suspended_notification_text" /> <java-symbol type="string" name="factory_reset_warning" /> <java-symbol type="string" name="factory_reset_message" /> <java-symbol type="string" name="lockscreen_transport_play_description" /> @@ -3820,6 +3825,9 @@ <java-symbol type="dimen" name="waterfall_display_right_edge_size" /> <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" /> + <!-- For device policy --> + <java-symbol type="array" name="config_packagesExemptFromSuspension" /> + <!-- Accessibility take screenshot --> <java-symbol type="string" name="capability_desc_canTakeScreenshot" /> <java-symbol type="string" name="capability_title_canTakeScreenshot" /> @@ -3831,4 +3839,12 @@ <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" /> <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" /> + + <!-- Intent resolver and share sheet --> + <java-symbol type="color" name="resolver_tabs_active_color" /> + <java-symbol type="color" name="resolver_tabs_inactive_color" /> + <java-symbol type="string" name="resolver_personal_tab" /> + <java-symbol type="string" name="resolver_work_tab" /> + <java-symbol type="id" name="stub" /> + </resources> diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index c086421501b5..411868d8befe 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -31,6 +31,7 @@ import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FRO import static com.android.internal.app.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST; import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST; import static com.android.internal.app.ChooserWrapperActivity.sOverrides; +import static com.android.internal.app.MatcherUtils.first; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -63,6 +64,8 @@ import android.graphics.Paint; import android.graphics.drawable.Icon; import android.metrics.LogMaker; import android.net.Uri; +import android.os.Bundle; +import android.os.UserHandle; import android.service.chooser.ChooserTarget; import androidx.test.platform.app.InstrumentationRegistry; @@ -74,7 +77,11 @@ import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -302,6 +309,7 @@ public class ChooserActivityTest { assertThat(activity.getIsSelected(), is(true)); } + @Ignore // b/148158199 @Test public void noResultsFromPackageManager() { when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), @@ -346,6 +354,9 @@ public class ChooserActivityTest { @Test public void hasOtherProfileOneOption() throws Exception { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2); @@ -372,9 +383,7 @@ public class ChooserActivityTest { // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(2); - // Check that the "Other Profile" activity is put in the right spot - onView(withId(R.id.profile_button)).check(matches( - withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name))); + waitForIdle(); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); @@ -383,6 +392,9 @@ public class ChooserActivityTest { @Test public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); @@ -411,9 +423,6 @@ public class ChooserActivityTest { // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(3); - // Check that the "Other Profile" activity is put in the right spot - onView(withId(R.id.profile_button)).check(matches( - withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name))); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); @@ -422,6 +431,9 @@ public class ChooserActivityTest { @Test public void hasLastChosenActivityAndOtherProfile() throws Exception { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendTextIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); @@ -448,9 +460,6 @@ public class ChooserActivityTest { // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(3); - // Check that the "Other Profile" activity is put in the right spot - onView(withId(R.id.profile_button)).check(matches( - withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name))); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); @@ -1161,6 +1170,123 @@ public class ChooserActivityTest { .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(-1)); } + @Test + public void testWorkTab_displayedWhenWorkProfileUserAvailable() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + markWorkProfileUserAvailable(); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + + onView(withId(R.id.tabs)).check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + + onView(withId(R.id.tabs)).check(matches(not(isDisplayed()))); + } + + @Test + public void testWorkTab_eachTabUsesExpectedAdapter() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + int personalProfileTargets = 3; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(personalProfileTargets); + int workProfileTargets = 4; + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( + workProfileTargets); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + markWorkProfileUserAvailable(); + + final ChooserWrapperActivity activity = + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + + assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); + // The work list adapter must only be filled when we open the work tab + assertThat(activity.getWorkListAdapter().getCount(), is(0)); + onView(withText(R.string.resolver_work_tab)).perform(click()); + assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); + assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets)); + assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); + } + + @Test + public void testWorkTab_workProfileHasExpectedNumberOfTargets() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + final ChooserWrapperActivity activity = + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); + } + + @Ignore // b/148156663 + @Test + public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + // wait for the share sheet to expand + Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); + + onView(first(withText(workResolvedComponentInfos.get(0) + .getResolveInfoAt(0).activityInfo.applicationInfo.name))) + .perform(click()); + waitForIdle(); + assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); + } + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); @@ -1224,6 +1350,15 @@ public class ChooserActivityTest { return infoList; } + private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId( + int numberOfResults, int userId) { + List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); + for (int i = 0; i < numberOfResults; i++) { + infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId)); + } + return infoList; + } + private List<ChooserTarget> createDirectShareTargets(int numberOfResults, String packageName) { Icon icon = Icon.createWithBitmap(createBitmap()); String testTitle = "testTitle"; @@ -1308,4 +1443,8 @@ public class ChooserActivityTest { assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString()); } } + + private void markWorkProfileUserAvailable() { + sOverrides.workProfileUserHandle = UserHandle.of(10); + } } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 2a1044361d42..eee62bb791bf 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -17,6 +17,7 @@ package com.android.internal.app; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.app.usage.UsageStatsManager; @@ -29,6 +30,7 @@ import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Bundle; import android.os.UserHandle; import android.util.Size; @@ -51,6 +53,19 @@ public class ChooserWrapperActivity extends ChooserActivity { return mChooserMultiProfilePagerAdapter.getActiveListAdapter(); } + ChooserListAdapter getPersonalListAdapter() { + return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0)) + .getListAdapter(); + } + + ChooserListAdapter getWorkListAdapter() { + if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { + return null; + } + return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1)) + .getListAdapter(); + } + boolean getIsSelected() { return mIsSuccessfullySelected; } UsageStatsManager getUsageStatsManager() { @@ -79,7 +94,12 @@ public class ChooserWrapperActivity extends ChooserActivity { @Override protected ResolverListController createListController(UserHandle userHandle) { - return sOverrides.resolverListController; + if (userHandle == UserHandle.SYSTEM) { + when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM); + return sOverrides.resolverListController; + } + when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle); + return sOverrides.workResolverListController; } @Override @@ -144,6 +164,15 @@ public class ChooserWrapperActivity extends ChooserActivity { resolveInfoPresentationGetter); } + @Override + protected UserHandle getWorkProfileUserHandle() { + return sOverrides.workProfileUserHandle; + } + + protected UserHandle getCurrentUserHandle() { + return mMultiProfilePagerAdapter.getCurrentUserHandle(); + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> @@ -154,6 +183,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public Function<PackageManager, PackageManager> createPackageManager; public Function<TargetInfo, Boolean> onSafelyStartCallback; public ResolverListController resolverListController; + public ResolverListController workResolverListController; public Boolean isVoiceInteraction; public boolean isImageType; public Cursor resolverCursor; @@ -162,6 +192,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public MetricsLogger metricsLogger; public int alternateProfileSetting; public Resources resources; + public UserHandle workProfileUserHandle; public void reset() { onSafelyStartCallback = null; @@ -172,9 +203,11 @@ public class ChooserWrapperActivity extends ChooserActivity { resolverCursor = null; resolverForceException = false; resolverListController = mock(ResolverListController.class); + workResolverListController = mock(ResolverListController.class); metricsLogger = mock(MetricsLogger.class); alternateProfileSetting = 0; resources = null; + workProfileUserHandle = null; } } } diff --git a/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java b/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java new file mode 100644 index 000000000000..a4766318a21e --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java @@ -0,0 +1,51 @@ +/* + * 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.internal.app; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +/** + * Utils for helping with more customized matching options, for example matching the first + * occurrence of a set criteria. + */ +public class MatcherUtils { + + /** + * Returns a {@link Matcher} which only matches the first occurrence of a set criteria. + */ + static <T> Matcher<T> first(final Matcher<T> matcher) { + return new BaseMatcher<T>() { + boolean isFirstMatch = true; + + @Override + public boolean matches(final Object item) { + if (isFirstMatch && matcher.matches(item)) { + isFirstMatch = false; + return true; + } + return false; + } + + @Override + public void describeTo(final Description description) { + description.appendText("Returns the first matching item"); + } + }; + } +} diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 923ce3e3935d..42f7736d37b0 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -19,13 +19,17 @@ package com.android.internal.app; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static com.android.internal.app.MatcherUtils.first; import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; +import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -33,6 +37,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.os.UserHandle; import android.text.TextUtils; import android.view.View; import android.widget.RelativeLayout; @@ -49,6 +54,9 @@ import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGett import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter; import com.android.internal.widget.ResolverDrawerLayout; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -212,6 +220,9 @@ public class ResolverActivityTest { @Test public void hasOtherProfileOneOption() throws Exception { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendImageIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2); @@ -237,9 +248,6 @@ public class ResolverActivityTest { // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(2); - // Check that the "Other Profile" activity is put in the right spot - onView(withId(R.id.profile_button)).check(matches( - withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name))); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); onView(withId(R.id.button_once)) @@ -250,6 +258,9 @@ public class ResolverActivityTest { @Test public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendImageIntent(); List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); @@ -279,9 +290,6 @@ public class ResolverActivityTest { List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(2); - // Check that the "Other Profile" activity is put in the right spot - onView(withId(R.id.profile_button)).check(matches( - withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name))); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); onView(withId(R.id.button_once)).perform(click()); @@ -292,6 +300,9 @@ public class ResolverActivityTest { @Test public void hasLastChosenActivityAndOtherProfile() throws Exception { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + // In this case we prefer the other profile and don't display anything about the last // chosen activity. Intent sendIntent = createSendImageIntent(); @@ -325,9 +336,6 @@ public class ResolverActivityTest { List<ResolvedComponentInfo> stableCopy = createResolvedComponentsForTestWithOtherProfile(2); - // Check that the "Other Profile" activity is put in the right spot - onView(withId(R.id.profile_button)).check(matches( - withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name))); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); onView(withId(R.id.button_once)).perform(click()); @@ -379,6 +387,222 @@ public class ResolverActivityTest { TextUtils.isEmpty(pg.getSubLabel())); } + @Test + public void testWorkTab_displayedWhenWorkProfileUserAvailable() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendImageIntent(); + markWorkProfileUserAvailable(); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + onView(withId(R.id.tabs)).check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + Intent sendIntent = createSendImageIntent(); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + onView(withId(R.id.tabs)).check(matches(not(isDisplayed()))); + } + + @Test + public void testWorkTab_workTabListEmptyBeforeGoingToTab() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + markWorkProfileUserAvailable(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); + // The work list adapter must only be filled when we open the work tab + assertThat(activity.getWorkListAdapter().getCount(), is(0)); + } + + @Test + public void testWorkTab_workTabUsesExpectedAdapter() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + markWorkProfileUserAvailable(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + + assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); + assertThat(activity.getWorkListAdapter().getCount(), is(4)); + } + + @Test + public void testWorkTab_personalTabUsesExpectedAdapter() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + markWorkProfileUserAvailable(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + + assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); + assertThat(activity.getPersonalListAdapter().getCount(), is(3)); + } + + @Test + public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)) + .perform(click()); + + waitForIdle(); + assertThat(activity.getWorkListAdapter().getCount(), is(4)); + } + + @Test + public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)) + .perform(click()); + waitForIdle(); + // wait for the share sheet to expand + Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); + onView(first(allOf(withText(workResolvedComponentInfos.get(0) + .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) + .perform(click()); + onView(withId(R.id.button_once)) + .perform(click()); + + waitForIdle(); + assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); + } + + @Test + public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets() + throws InterruptedException { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)) + .perform(click()); + + waitForIdle(); + assertThat(activity.getWorkListAdapter().getCount(), is(4)); + } + + @Ignore // b/148156663 + @Test + public void testWorkTab_noPersonalApps_canStartWorkApps() + throws InterruptedException { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)) + .perform(click()); + waitForIdle(); + // wait for the share sheet to expand + Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); + onView(first(allOf(withText(workResolvedComponentInfos.get(0) + .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) + .perform(click()); + onView(withId(R.id.button_once)) + .perform(click()); + waitForIdle(); + + assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); + } + private Intent createSendImageIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); @@ -411,4 +635,8 @@ public class ResolverActivityTest { private void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } + + private void markWorkProfileUserAvailable() { + ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10); + } } diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java index 59634f6d261c..d7db5f8e46eb 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java @@ -46,6 +46,12 @@ class ResolverDataProvider { createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE)); } + static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i, + int userId) { + return new ResolverActivity.ResolvedComponentInfo(createComponentName(i), + createResolverIntent(i), createResolveInfo(i, userId)); + } + static ComponentName createComponentName(int i) { final String name = "component" + i; return new ComponentName("foo.bar." + name, name); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java index c5d2cfaa9512..36c8724e522e 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java @@ -17,12 +17,14 @@ package com.android.internal.app; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.Bundle; import android.os.UserHandle; import com.android.internal.app.chooser.TargetInfo; @@ -49,6 +51,17 @@ public class ResolverWrapperActivity extends ResolverActivity { return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter(); } + ResolverListAdapter getPersonalListAdapter() { + return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0)); + } + + ResolverListAdapter getWorkListAdapter() { + if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { + return null; + } + return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1)); + } + @Override public boolean isVoiceInteraction() { if (sOverrides.isVoiceInteraction != null) { @@ -68,7 +81,12 @@ public class ResolverWrapperActivity extends ResolverActivity { @Override protected ResolverListController createListController(UserHandle userHandle) { - return sOverrides.resolverListController; + if (userHandle == UserHandle.SYSTEM) { + when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM); + return sOverrides.resolverListController; + } + when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle); + return sOverrides.workResolverListController; } @Override @@ -79,6 +97,20 @@ public class ResolverWrapperActivity extends ResolverActivity { return super.getPackageManager(); } + protected UserHandle getCurrentUserHandle() { + return mMultiProfilePagerAdapter.getCurrentUserHandle(); + } + + @Override + protected UserHandle getWorkProfileUserHandle() { + return sOverrides.workProfileUserHandle; + } + + @Override + public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { + super.startActivityAsUser(intent, options, user); + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> @@ -89,13 +121,17 @@ public class ResolverWrapperActivity extends ResolverActivity { public Function<PackageManager, PackageManager> createPackageManager; public Function<TargetInfo, Boolean> onSafelyStartCallback; public ResolverListController resolverListController; + public ResolverListController workResolverListController; public Boolean isVoiceInteraction; + public UserHandle workProfileUserHandle; public void reset() { onSafelyStartCallback = null; isVoiceInteraction = null; createPackageManager = null; resolverListController = mock(ResolverListController.class); + workResolverListController = mock(ResolverListController.class); + workProfileUserHandle = null; } } }
\ No newline at end of file diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in index 4a252afc1df3..49817925d9b4 100644 --- a/libs/hwui/DisplayListOps.in +++ b/libs/hwui/DisplayListOps.in @@ -14,39 +14,41 @@ * limitations under the License. */ -X(Flush) -X(Save) -X(Restore) +X(Flush) +X(Save) +X(Restore) X(SaveLayer) X(SaveBehind) -X(Concat) -X(SetMatrix) +X(Concat44) +X(Concat) +X(SetMatrix) +X(Scale) X(Translate) -X(ClipPath) -X(ClipRect) -X(ClipRRect) +X(ClipPath) +X(ClipRect) +X(ClipRRect) X(ClipRegion) X(DrawPaint) X(DrawBehind) -X(DrawPath) -X(DrawRect) -X(DrawRegion) -X(DrawOval) +X(DrawPath) +X(DrawRect) +X(DrawRegion) +X(DrawOval) X(DrawArc) -X(DrawRRect) -X(DrawDRRect) -X(DrawAnnotation) -X(DrawDrawable) +X(DrawRRect) +X(DrawDRRect) +X(DrawAnnotation) +X(DrawDrawable) X(DrawPicture) -X(DrawImage) -X(DrawImageNine) -X(DrawImageRect) +X(DrawImage) +X(DrawImageNine) +X(DrawImageRect) X(DrawImageLattice) X(DrawTextBlob) -X(DrawPatch) -X(DrawPoints) -X(DrawVertices) -X(DrawAtlas) +X(DrawPatch) +X(DrawPoints) +X(DrawVertices) +X(DrawAtlas) X(DrawShadowRec) X(DrawVectorDrawable) X(DrawWebView) diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index c0df2faf120a..dc467c41baed 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -130,6 +130,12 @@ struct SaveBehind final : Op { } }; +struct Concat44 final : Op { + static const auto kType = Type::Concat44; + Concat44(const SkScalar m[16]) { memcpy(colMajor, m, sizeof(colMajor)); } + SkScalar colMajor[16]; + void draw(SkCanvas* c, const SkMatrix&) const { c->experimental_concat44(colMajor); } +}; struct Concat final : Op { static const auto kType = Type::Concat; Concat(const SkMatrix& matrix) : matrix(matrix) {} @@ -144,6 +150,12 @@ struct SetMatrix final : Op { c->setMatrix(SkMatrix::Concat(original, matrix)); } }; +struct Scale final : Op { + static const auto kType = Type::Scale; + Scale(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} + SkScalar sx, sy; + void draw(SkCanvas* c, const SkMatrix&) const { c->scale(sx, sy); } +}; struct Translate final : Op { static const auto kType = Type::Translate; Translate(SkScalar dx, SkScalar dy) : dx(dx), dy(dy) {} @@ -562,12 +574,18 @@ void DisplayListData::saveBehind(const SkRect* subset) { this->push<SaveBehind>(0, subset); } +void DisplayListData::concat44(const SkScalar colMajor[16]) { + this->push<Concat44>(0, colMajor); +} void DisplayListData::concat(const SkMatrix& matrix) { this->push<Concat>(0, matrix); } void DisplayListData::setMatrix(const SkMatrix& matrix) { this->push<SetMatrix>(0, matrix); } +void DisplayListData::scale(SkScalar sx, SkScalar sy) { + this->push<Scale>(0, sx, sy); +} void DisplayListData::translate(SkScalar dx, SkScalar dy) { this->push<Translate>(0, dx, dy); } @@ -823,12 +841,18 @@ bool RecordingCanvas::onDoSaveBehind(const SkRect* subset) { return false; } +void RecordingCanvas::didConcat44(const SkScalar colMajor[16]) { + fDL->concat44(colMajor); +} void RecordingCanvas::didConcat(const SkMatrix& matrix) { fDL->concat(matrix); } void RecordingCanvas::didSetMatrix(const SkMatrix& matrix) { fDL->setMatrix(matrix); } +void RecordingCanvas::didScale(SkScalar sx, SkScalar sy) { + fDL->scale(sx, sy); +} void RecordingCanvas::didTranslate(SkScalar dx, SkScalar dy) { fDL->translate(dx, dy); } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 322eff24dd34..7eb1ce3eb18a 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -82,8 +82,10 @@ private: void saveBehind(const SkRect*); void restore(); + void concat44(const SkScalar colMajor[16]); void concat(const SkMatrix&); void setMatrix(const SkMatrix&); + void scale(SkScalar, SkScalar); void translate(SkScalar, SkScalar); void translateZ(SkScalar); @@ -153,8 +155,10 @@ public: void onFlush() override; + void didConcat44(const SkScalar[16]) override; void didConcat(const SkMatrix&) override; void didSetMatrix(const SkMatrix&) override; + void didScale(SkScalar, SkScalar) override; void didTranslate(SkScalar, SkScalar) override; void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index a1b2b18195bc..aa8849b642b1 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -26,6 +26,7 @@ #include "SkAndroidFrameworkUtils.h" #include "SkClipStack.h" #include "SkRect.h" +#include "include/private/SkM44.h" namespace android { namespace uirenderer { @@ -92,7 +93,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { SkIRect surfaceBounds = canvas->internal_private_getTopLayerBounds(); SkIRect clipBounds = canvas->getDeviceClipBounds(); - SkMatrix44 mat4(canvas->getTotalMatrix()); + SkM44 mat4(canvas->experimental_getLocalToDevice()); SkRegion clipRegion; canvas->temporary_internal_getRgnClip(&clipRegion); @@ -118,7 +119,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // update the matrix and clip that we pass to the WebView to match the coordinates of // the offscreen layer - mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop, 0); + mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop); clipBounds.offsetTo(0, 0); clipRegion.translate(-surfaceBounds.fLeft, -surfaceBounds.fTop); @@ -126,7 +127,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // we are drawing into a (clipped) offscreen layer so we must update the clip and matrix // from device coordinates to the layer's coordinates clipBounds.offset(-surfaceBounds.fLeft, -surfaceBounds.fTop); - mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop, 0); + mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop); } DrawGlInfo info; @@ -137,7 +138,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { info.isLayer = fboID != 0; info.width = fboSize.width(); info.height = fboSize.height(); - mat4.asColMajorf(&info.transform[0]); + mat4.getColMajor(&info.transform[0]); info.color_space_ptr = canvas->imageInfo().colorSpace(); // ensure that the framebuffer that the webview will render into is bound before we clear diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp index 112792611fc3..68f111752a4c 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -20,6 +20,7 @@ #include <GrBackendDrawableInfo.h> #include <SkAndroidFrameworkUtils.h> #include <SkImage.h> +#include "include/private/SkM44.h" #include <utils/Color.h> #include <utils/Trace.h> #include <utils/TraceUtils.h> @@ -62,7 +63,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { renderthread::RenderThread::getInstance().vulkanManager(); mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams()); - SkMatrix44 mat4(mMatrix); + SkM44 mat4(mMatrix); VkFunctorDrawParams params{ .width = mImageInfo.width(), .height = mImageInfo.height(), @@ -72,7 +73,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { .clip_right = mClip.fRight, .clip_bottom = mClip.fBottom, }; - mat4.asColMajorf(¶ms.transform[0]); + mat4.getColMajor(¶ms.transform[0]); params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer; params.color_attachment_index = vulkan_info.fColorAttachmentIndex; params.compatible_render_pass = vulkan_info.fCompatibleRenderPass; diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index 706325f00bd2..241d3708def5 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -121,7 +121,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { glBindTexture(GL_TEXTURE_2D, 0); DrawGlInfo info; - SkMatrix44 mat4(canvas->getTotalMatrix()); + SkM44 mat4(canvas->experimental_getLocalToDevice()); SkIRect clipBounds = canvas->getDeviceClipBounds(); info.clipLeft = clipBounds.fLeft; @@ -131,7 +131,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { info.isLayer = true; info.width = mFBInfo.width(); info.height = mFBInfo.height(); - mat4.asColMajorf(&info.transform[0]); + mat4.getColMajor(&info.transform[0]); info.color_space_ptr = canvas->imageInfo().colorSpace(); glViewport(0, 0, info.width, info.height); diff --git a/media/java/android/media/AudioPlaybackCaptureConfiguration.java b/media/java/android/media/AudioPlaybackCaptureConfiguration.java index 453704eea398..65f2f1789491 100644 --- a/media/java/android/media/AudioPlaybackCaptureConfiguration.java +++ b/media/java/android/media/AudioPlaybackCaptureConfiguration.java @@ -102,6 +102,12 @@ public final class AudioPlaybackCaptureConfiguration { criterion -> criterion.getIntProp()); } + /** @return the userId's passed to {@link Builder#addMatchingUserId(int)}. */ + public @NonNull int[] getMatchingUserIds() { + return getIntPredicates(AudioMixingRule.RULE_MATCH_USERID, + criterion -> criterion.getIntProp()); + } + /** @return the usages passed to {@link Builder#excludeUsage(int)}. */ @AttributeUsage public @NonNull int[] getExcludeUsages() { @@ -115,6 +121,12 @@ public final class AudioPlaybackCaptureConfiguration { criterion -> criterion.getIntProp()); } + /** @return the userId's passed to {@link Builder#excludeUserId(int)}. */ + public @NonNull int[] getExcludeUserIds() { + return getIntPredicates(AudioMixingRule.RULE_EXCLUDE_USERID, + criterion -> criterion.getIntProp()); + } + private int[] getIntPredicates(int rule, ToIntFunction<AudioMixMatchCriterion> getPredicate) { return mAudioMixingRule.getCriteria().stream() @@ -153,6 +165,7 @@ public final class AudioPlaybackCaptureConfiguration { private final MediaProjection mProjection; private int mUsageMatchType = MATCH_TYPE_UNSPECIFIED; private int mUidMatchType = MATCH_TYPE_UNSPECIFIED; + private int mUserIdMatchType = MATCH_TYPE_UNSPECIFIED; /** @param projection A MediaProjection that supports audio projection. */ public Builder(@NonNull MediaProjection projection) { @@ -202,6 +215,23 @@ public final class AudioPlaybackCaptureConfiguration { } /** + * Only capture audio output by app with the matching {@code userId}. + * + * <p>If called multiple times, will capture audio output by apps whose userId is any of the + * given userId's. + * + * @throws IllegalStateException if called in conjunction with {@link #excludeUserId(int)}. + */ + public @NonNull Builder addMatchingUserId(int userId) { + Preconditions.checkState( + mUserIdMatchType != MATCH_TYPE_EXCLUSIVE, + ERROR_MESSAGE_MISMATCHED_RULES); + mAudioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_USERID, userId); + mUserIdMatchType = MATCH_TYPE_INCLUSIVE; + return this; + } + + /** * Only capture audio output that does not match the given {@link AudioAttributes}. * * <p>If called multiple times, will capture audio output that does not match any of the @@ -238,6 +268,24 @@ public final class AudioPlaybackCaptureConfiguration { } /** + * Only capture audio output by apps that do not have the matching {@code userId}. + * + * <p>If called multiple times, will capture audio output by apps whose userId is not any of + * the given userId's. + * + * @throws IllegalStateException if called in conjunction with + * {@link #addMatchingUserId(int)}. + */ + public @NonNull Builder excludeUserId(int userId) { + Preconditions.checkState( + mUserIdMatchType != MATCH_TYPE_INCLUSIVE, + ERROR_MESSAGE_MISMATCHED_RULES); + mAudioMixingRuleBuilder.excludeMixRule(AudioMixingRule.RULE_MATCH_USERID, userId); + mUserIdMatchType = MATCH_TYPE_EXCLUSIVE; + return this; + } + + /** * Builds the configuration instance. * * @throws UnsupportedOperationException if the parameters set are incompatible. diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 48d27faa0855..02cb8aafea0c 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1169,6 +1169,13 @@ public class AudioSystem /** see AudioPolicy.removeUidDeviceAffinities() */ public static native int removeUidDeviceAffinities(int uid); + /** see AudioPolicy.setUserIdDeviceAffinities() */ + public static native int setUserIdDeviceAffinities(int userId, @NonNull int[] types, + @NonNull String[] addresses); + + /** see AudioPolicy.removeUserIdDeviceAffinities() */ + public static native int removeUserIdDeviceAffinities(int userId); + public static native int systemReady(); public static native float getStreamVolumeDB(int stream, int index, int device); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 7f1c69218f45..1f97be5c3f4d 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -266,6 +266,10 @@ interface IAudioService { int removeUidDeviceAffinity(in IAudioPolicyCallback pcb, in int uid); + int setUserIdDeviceAffinity(in IAudioPolicyCallback pcb, in int userId, in int[] deviceTypes, + in String[] deviceAddresses); + int removeUserIdDeviceAffinity(in IAudioPolicyCallback pcb, in int userId); + boolean hasHapticChannels(in Uri uri); boolean isCallScreeningModeSupported(); diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java index 8c204d222cd4..bca3fa7834b4 100644 --- a/media/java/android/media/audiopolicy/AudioMixingRule.java +++ b/media/java/android/media/audiopolicy/AudioMixingRule.java @@ -73,6 +73,12 @@ public class AudioMixingRule { * parameter is an instance of {@link java.lang.Integer}. */ public static final int RULE_MATCH_UID = 0x1 << 2; + /** + * A rule requiring the userId of the audio stream to match that specified. + * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object + * parameter is an instance of {@link java.lang.Integer}. + */ + public static final int RULE_MATCH_USERID = 0x1 << 3; private final static int RULE_EXCLUSION_MASK = 0x8000; /** @@ -94,6 +100,13 @@ public class AudioMixingRule { public static final int RULE_EXCLUDE_UID = RULE_EXCLUSION_MASK | RULE_MATCH_UID; + /** + * @hide + * A rule requiring the userId information to differ. + */ + public static final int RULE_EXCLUDE_USERID = + RULE_EXCLUSION_MASK | RULE_MATCH_USERID; + /** @hide */ public static final class AudioMixMatchCriterion { @UnsupportedAppUsage @@ -125,19 +138,20 @@ public class AudioMixingRule { dest.writeInt(mRule); final int match_rule = mRule & ~RULE_EXCLUSION_MASK; switch (match_rule) { - case RULE_MATCH_ATTRIBUTE_USAGE: - dest.writeInt(mAttr.getUsage()); - break; - case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: - dest.writeInt(mAttr.getCapturePreset()); - break; - case RULE_MATCH_UID: - dest.writeInt(mIntProp); - break; - default: - Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule - + " when writing to Parcel"); - dest.writeInt(-1); + case RULE_MATCH_ATTRIBUTE_USAGE: + dest.writeInt(mAttr.getUsage()); + break; + case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: + dest.writeInt(mAttr.getCapturePreset()); + break; + case RULE_MATCH_UID: + case RULE_MATCH_USERID: + dest.writeInt(mIntProp); + break; + default: + Log.e("AudioMixMatchCriterion", "Unknown match rule" + match_rule + + " when writing to Parcel"); + dest.writeInt(-1); } } @@ -203,6 +217,7 @@ public class AudioMixingRule { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: case RULE_MATCH_UID: + case RULE_MATCH_USERID: return true; default: return false; @@ -225,6 +240,7 @@ public class AudioMixingRule { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: case RULE_MATCH_UID: + case RULE_MATCH_USERID: return true; default: return false; @@ -234,11 +250,12 @@ public class AudioMixingRule { private static boolean isPlayerRule(int rule) { final int match_rule = rule & ~RULE_EXCLUSION_MASK; switch (match_rule) { - case RULE_MATCH_ATTRIBUTE_USAGE: - case RULE_MATCH_UID: - return true; - default: - return false; + case RULE_MATCH_ATTRIBUTE_USAGE: + case RULE_MATCH_UID: + case RULE_MATCH_USERID: + return true; + default: + return false; } } @@ -319,7 +336,8 @@ public class AudioMixingRule { * property to match against. * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or - * {@link AudioMixingRule#RULE_MATCH_UID}. + * {@link AudioMixingRule#RULE_MATCH_UID} or + * {@link AudioMixingRule#RULE_MATCH_USERID}. * @param property see the definition of each rule for the type to use (either an * {@link AudioAttributes} or an {@link java.lang.Integer}). * @return the same Builder instance. @@ -349,7 +367,8 @@ public class AudioMixingRule { * coming from the specified UID. * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or - * {@link AudioMixingRule#RULE_MATCH_UID}. + * {@link AudioMixingRule#RULE_MATCH_UID} or + * {@link AudioMixingRule#RULE_MATCH_USERID}. * @param property see the definition of each rule for the type to use (either an * {@link AudioAttributes} or an {@link java.lang.Integer}). * @return the same Builder instance. @@ -425,6 +444,8 @@ public class AudioMixingRule { * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}, * {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}. + * {@link AudioMixingRule#RULE_MATCH_USERID}, + * {@link AudioMixingRule#RULE_EXCLUDE_USERID}. * @return the same Builder instance. * @throws IllegalArgumentException */ @@ -495,6 +516,20 @@ public class AudioMixingRule { } } break; + case RULE_MATCH_USERID: + // "userid"-based rule + if (criterion.mIntProp == intProp.intValue()) { + if (criterion.mRule == rule) { + // rule already exists, we're done + return this; + } else { + // criterion already exists with a another rule, + // it is incompatible + throw new IllegalArgumentException("Contradictory rule exists" + + " for userId " + intProp); + } + } + break; } } // rule didn't exist, add it @@ -504,6 +539,7 @@ public class AudioMixingRule { mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule)); break; case RULE_MATCH_UID: + case RULE_MATCH_USERID: mCriteria.add(new AudioMixMatchCriterion(intProp, rule)); break; default: @@ -530,6 +566,7 @@ public class AudioMixingRule { .setInternalCapturePreset(preset).build(); break; case RULE_MATCH_UID: + case RULE_MATCH_USERID: intProp = new Integer(in.readInt()); break; default: diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 27f02fe528f3..32a4a4f70192 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -51,6 +51,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @hide @@ -404,7 +405,7 @@ public class AudioPolicy { /** * @hide - * Configures the audio framework so that all audio stream originating from the given UID + * Configures the audio framework so that all audio streams originating from the given UID * can only come from a set of audio devices. * For this routing to be operational, a number of {@link AudioMix} instances must have been * previously registered on this policy, and routed to a super-set of the given audio devices @@ -476,6 +477,78 @@ public class AudioPolicy { } } + /** + * @hide + * Removes audio device affinity previously set by + * {@link #setUserIdDeviceAffinity(int, java.util.List)}. + * @param userId userId of the application affected. + * @return true if the change was successful, false otherwise. + */ + @TestApi + @SystemApi + public boolean removeUserIdDeviceAffinity(int userId) { + synchronized (mLock) { + if (mStatus != POLICY_STATUS_REGISTERED) { + throw new IllegalStateException("Cannot use unregistered AudioPolicy"); + } + final IAudioService service = getService(); + try { + final int status = service.removeUserIdDeviceAffinity(this.cb(), userId); + return (status == AudioManager.SUCCESS); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in removeUserIdDeviceAffinity", e); + return false; + } + } + } + + /** + * @hide + * Configures the audio framework so that all audio streams originating from the given user + * can only come from a set of audio devices. + * For this routing to be operational, a number of {@link AudioMix} instances must have been + * previously registered on this policy, and routed to a super-set of the given audio devices + * with {@link AudioMix.Builder#setDevice(android.media.AudioDeviceInfo)}. Note that having + * multiple devices in the list doesn't imply the signals will be duplicated on the different + * audio devices, final routing will depend on the {@link AudioAttributes} of the sounds being + * played. + * @param userId Android user id to affect. + * @param devices list of devices to which the audio stream of the application may be routed. + * @return true if the change was successful, false otherwise. + */ + @TestApi + @SystemApi + public boolean setUserIdDeviceAffinity(int userId, @NonNull List<AudioDeviceInfo> devices) { + Objects.requireNonNull(devices, "Illegal null list of audio devices"); + synchronized (mLock) { + if (mStatus != POLICY_STATUS_REGISTERED) { + throw new IllegalStateException("Cannot use unregistered AudioPolicy"); + } + final int[] deviceTypes = new int[devices.size()]; + final String[] deviceAddresses = new String[devices.size()]; + int i = 0; + for (AudioDeviceInfo device : devices) { + if (device == null) { + throw new IllegalArgumentException( + "Illegal null AudioDeviceInfo in setUserIdDeviceAffinity"); + } + deviceTypes[i] = + AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()); + deviceAddresses[i] = device.getAddress(); + i++; + } + final IAudioService service = getService(); + try { + final int status = service.setUserIdDeviceAffinity(this.cb(), + userId, deviceTypes, deviceAddresses); + return (status == AudioManager.SUCCESS); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in setUserIdDeviceAffinity", e); + return false; + } + } + } + public void setRegistration(String regId) { synchronized (mLock) { mRegistrationId = regId; diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index c4ba0c1fc835..b048158c3979 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -197,6 +197,14 @@ public class AudioPolicyConfig implements Parcelable { textDump += " exclude UID "; textDump += criterion.mIntProp; break; + case AudioMixingRule.RULE_MATCH_USERID: + textDump += " match userId "; + textDump += criterion.mIntProp; + break; + case AudioMixingRule.RULE_EXCLUDE_USERID: + textDump += " exclude userId "; + textDump += criterion.mIntProp; + break; default: textDump += "invalid rule!"; } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index ad802ff879f2..54b420191deb 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -312,5 +312,9 @@ message SystemMessage { // Notify the user that data or apps are being moved to external storage. // Package: com.android.systemui NOTE_STORAGE_MOVE = 0x534d4f56; + + // Notify the user that the admin suspended personal apps on the device. + // Package: android + NOTE_PERSONAL_APPS_SUSPENDED = 1003; } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index e976811a3094..434a97ee0bc5 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -361,13 +361,18 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } @Override - public boolean isDeviceAssociated(String packageName, String macAddress, int userId) { + public boolean isDeviceAssociatedForWifiConnection(String packageName, String macAddress, + int userId) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_COMPANION_DEVICES, "isDeviceAssociated"); + boolean bypassMacPermission = getContext().getPackageManager().checkPermission( + android.Manifest.permission.COMPANION_APPROVE_WIFI_CONNECTIONS, packageName) + == PackageManager.PERMISSION_GRANTED; + return CollectionUtils.any( readAllAssociations(userId, packageName), - a -> Objects.equals(a.deviceAddress, macAddress)); + a -> bypassMacPermission || Objects.equals(a.deviceAddress, macAddress)); } private void checkCanCallNotificationApi(String callingPackage) throws RemoteException { diff --git a/services/core/Android.bp b/services/core/Android.bp index 1691a96b0dc4..a603fa975b74 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -113,6 +113,7 @@ java_library_static { "android.hardware.broadcastradio-V2.0-java", "android.hardware.health-V1.0-java", "android.hardware.health-V2.0-java", + "android.hardware.light-java", "android.hardware.weaver-V1.0-java", "android.hardware.biometrics.face-V1.0-java", "android.hardware.biometrics.fingerprint-V2.1-java", diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index a33fcd557369..8074900d2776 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -66,8 +66,8 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.DumpUtils; import com.android.server.am.BatteryStatsService; -import com.android.server.lights.Light; import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; import java.io.File; import java.io.FileDescriptor; @@ -1065,7 +1065,7 @@ public final class BatteryService extends SystemService { } private final class Led { - private final Light mBatteryLight; + private final LogicalLight mBatteryLight; private final int mBatteryLowARGB; private final int mBatteryMediumARGB; @@ -1100,7 +1100,7 @@ public final class BatteryService extends SystemService { mBatteryLight.setColor(mBatteryLowARGB); } else { // Flash red when battery is low and not charging - mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED, + mBatteryLight.setFlashing(mBatteryLowARGB, LogicalLight.LIGHT_FLASH_TIMED, mBatteryLedOn, mBatteryLedOff); } } else if (status == BatteryManager.BATTERY_STATUS_CHARGING diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 73ec43fb050a..8f6bd212da19 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -7041,6 +7041,26 @@ public class AudioService extends IAudioService.Stub } } + /** see AudioPolicy.setUserIdDeviceAffinity() */ + public int setUserIdDeviceAffinity(IAudioPolicyCallback pcb, int userId, + @NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) { + if (DEBUG_AP) { + Log.d(TAG, "setUserIdDeviceAffinity for " + pcb.asBinder() + " user:" + userId); + } + + synchronized (mAudioPolicies) { + final AudioPolicyProxy app = + checkUpdateForPolicy(pcb, "Cannot change device affinity in audio policy"); + if (app == null) { + return AudioManager.ERROR; + } + if (!app.hasMixRoutedToDevices(deviceTypes, deviceAddresses)) { + return AudioManager.ERROR; + } + return app.setUserIdDeviceAffinities(userId, deviceTypes, deviceAddresses); + } + } + /** see AudioPolicy.removeUidDeviceAffinity() */ public int removeUidDeviceAffinity(IAudioPolicyCallback pcb, int uid) { if (DEBUG_AP) { @@ -7056,6 +7076,22 @@ public class AudioService extends IAudioService.Stub } } + /** see AudioPolicy.removeUserIdDeviceAffinity() */ + public int removeUserIdDeviceAffinity(IAudioPolicyCallback pcb, int userId) { + if (DEBUG_AP) { + Log.d(TAG, "removeUserIdDeviceAffinity for " + pcb.asBinder() + + " userId:" + userId); + } + synchronized (mAudioPolicies) { + final AudioPolicyProxy app = + checkUpdateForPolicy(pcb, "Cannot remove device affinity in audio policy"); + if (app == null) { + return AudioManager.ERROR; + } + return app.removeUserIdDeviceAffinities(userId); + } + } + public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) { if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior + " policy " + pcb.asBinder()); @@ -7291,6 +7327,9 @@ public class AudioService extends IAudioService.Stub final HashMap<Integer, AudioDeviceArray> mUidDeviceAffinities = new HashMap<Integer, AudioDeviceArray>(); + final HashMap<Integer, AudioDeviceArray> mUserIdDeviceAffinities = + new HashMap<>(); + final IMediaProjection mProjection; private final class UnregisterOnStopCallback extends IMediaProjectionCallback.Stub { public void onStop() { @@ -7482,6 +7521,45 @@ public class AudioService extends IAudioService.Stub return AudioManager.ERROR; } + int setUserIdDeviceAffinities(int userId, + @NonNull int[] types, @NonNull String[] addresses) { + final Integer UserId = new Integer(userId); + int res; + if (mUserIdDeviceAffinities.remove(UserId) != null) { + final long identity = Binder.clearCallingIdentity(); + res = AudioSystem.removeUserIdDeviceAffinities(UserId); + Binder.restoreCallingIdentity(identity); + if (res != AudioSystem.SUCCESS) { + Log.e(TAG, "AudioSystem. removeUserIdDeviceAffinities(" + + UserId + ") failed, " + + " cannot call AudioSystem.setUserIdDeviceAffinities"); + return AudioManager.ERROR; + } + } + final long identity = Binder.clearCallingIdentity(); + res = AudioSystem.setUserIdDeviceAffinities(userId, types, addresses); + Binder.restoreCallingIdentity(identity); + if (res == AudioSystem.SUCCESS) { + mUserIdDeviceAffinities.put(UserId, new AudioDeviceArray(types, addresses)); + return AudioManager.SUCCESS; + } + Log.e(TAG, "AudioSystem.setUserIdDeviceAffinities(" + userId + ") failed"); + return AudioManager.ERROR; + } + + int removeUserIdDeviceAffinities(int userId) { + if (mUserIdDeviceAffinities.remove(new Integer(userId)) != null) { + final long identity = Binder.clearCallingIdentity(); + final int res = AudioSystem.removeUserIdDeviceAffinities(userId); + Binder.restoreCallingIdentity(identity); + if (res == AudioSystem.SUCCESS) { + return AudioManager.SUCCESS; + } + } + Log.e(TAG, "AudioSystem.removeUserIdDeviceAffinities failed"); + return AudioManager.ERROR; + } + /** @return human readable debug informations summarizing the state of the object. */ public String toLogFriendlyString() { String textDump = super.toLogFriendlyString(); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 1f17f9f1ca43..7e8fe3ae8428 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -41,8 +41,8 @@ import android.view.SurfaceControl; import com.android.internal.os.BackgroundThread; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; -import com.android.server.lights.Light; import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; import java.io.PrintWriter; import java.util.ArrayList; @@ -164,7 +164,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final class LocalDisplayDevice extends DisplayDevice { private final long mPhysicalDisplayId; - private final Light mBacklight; + private final LogicalLight mBacklight; private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>(); private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>(); private final boolean mIsInternal; diff --git a/services/core/java/com/android/server/lights/LightsManager.java b/services/core/java/com/android/server/lights/LightsManager.java index be20a445432b..521913a0c439 100644 --- a/services/core/java/com/android/server/lights/LightsManager.java +++ b/services/core/java/com/android/server/lights/LightsManager.java @@ -29,5 +29,8 @@ public abstract class LightsManager { public static final int LIGHT_ID_WIFI = Type.WIFI; public static final int LIGHT_ID_COUNT = Type.COUNT; - public abstract Light getLight(int id); + /** + * Returns the logical light with the given type. + */ + public abstract LogicalLight getLight(int id); } diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java index 8e6e1d6502de..5683e6901a31 100644 --- a/services/core/java/com/android/server/lights/LightsService.java +++ b/services/core/java/com/android/server/lights/LightsService.java @@ -15,32 +15,223 @@ package com.android.server.lights; +import android.Manifest; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; +import android.hardware.light.HwLight; +import android.hardware.light.HwLightState; +import android.hardware.light.ILights; +import android.hardware.lights.ILightsManager; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; import android.os.Handler; import android.os.IBinder; -import android.os.Message; +import android.os.Looper; import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.Trace; import android.provider.Settings; import android.util.Slog; +import android.util.SparseArray; import android.view.SurfaceControl; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.server.SystemService; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class LightsService extends SystemService { static final String TAG = "LightsService"; static final boolean DEBUG = false; - final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT]; + private LightImpl[] mLights = null; + private SparseArray<LightImpl> mLightsById = null; + + private ILights mVintfLights = null; + + @VisibleForTesting + final LightsManagerBinderService mManagerService; + + private Handler mH; + + private final class LightsManagerBinderService extends ILightsManager.Stub { + + private final class Session { + final IBinder mToken; + final SparseArray<LightState> mRequests = new SparseArray<>(); + + Session(IBinder token) { + mToken = token; + } + + void setRequest(int lightId, LightState state) { + if (state != null) { + mRequests.put(lightId, state); + } else { + mRequests.remove(lightId); + } + } + } + + @GuardedBy("LightsService.this") + private final List<Session> mSessions = new ArrayList<>(); + + /** + * Returns the lights available for apps to control on the device. Only lights that aren't + * reserved for system use are available to apps. + */ + @Override + public List<Light> getLights() { + getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS, + "getLights requires CONTROL_DEVICE_LIGHTS_PERMISSION"); + + synchronized (LightsService.this) { + final List<Light> lights = new ArrayList<Light>(); + for (int i = 0; i < mLightsById.size(); i++) { + HwLight hwLight = mLightsById.valueAt(i).getHwLight(); + if (!isSystemLight(hwLight)) { + lights.add(new Light(hwLight.id, hwLight.ordinal, hwLight.type)); + } + } + return lights; + } + } + + /** + * Updates the set of light requests for {@param token} with additions and removals from + * {@param lightIds} and {@param lightStates}. + * + * <p>Null values mean that the request should be removed, and the light turned off if it + * is not being used by anything else. + */ + @Override + public void setLightStates(IBinder token, int[] lightIds, LightState[] lightStates) { + getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS, + "setLightStates requires CONTROL_DEVICE_LIGHTS permission"); + Preconditions.checkState(lightIds.length == lightStates.length); + + synchronized (LightsService.this) { + Session session = getSessionLocked(Preconditions.checkNotNull(token)); + Preconditions.checkState(session != null, "not registered"); + + checkRequestIsValid(lightIds); + + for (int i = 0; i < lightIds.length; i++) { + session.setRequest(lightIds[i], lightStates[i]); + } + invalidateLightStatesLocked(); + } + } + + @Override + public @Nullable LightState getLightState(int lightId) { + getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS, + "getLightState(@TestApi) requires CONTROL_DEVICE_LIGHTS permission"); + + synchronized (LightsService.this) { + final LightImpl light = mLightsById.get(lightId); + if (light == null || isSystemLight(light.getHwLight())) { + throw new IllegalArgumentException("Invalid light: " + lightId); + } + return new LightState(light.getColor()); + } + } + + @Override + public void openSession(IBinder token) { + getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS, + "openSession requires CONTROL_DEVICE_LIGHTS permission"); + Preconditions.checkNotNull(token); + + synchronized (LightsService.this) { + Preconditions.checkState(getSessionLocked(token) == null, "already registered"); + try { + token.linkToDeath(() -> closeSessionInternal(token), 0); + mSessions.add(new Session(token)); + } catch (RemoteException e) { + Slog.e(TAG, "Couldn't open session, client already died" , e); + throw new IllegalArgumentException("Client is already dead."); + } + } + } + + @Override + public void closeSession(IBinder token) { + getContext().enforceCallingOrSelfPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS, + "closeSession requires CONTROL_DEVICE_LIGHTS permission"); + Preconditions.checkNotNull(token); + closeSessionInternal(token); + } + + private void closeSessionInternal(IBinder token) { + synchronized (LightsService.this) { + final Session session = getSessionLocked(token); + if (session != null) { + mSessions.remove(session); + invalidateLightStatesLocked(); + } + } + } + + private void checkRequestIsValid(int[] lightIds) { + for (int i = 0; i < lightIds.length; i++) { + final LightImpl light = mLightsById.get(lightIds[i]); + final HwLight hwLight = light.getHwLight(); + Preconditions.checkState(light != null && !isSystemLight(hwLight), + "invalid lightId " + hwLight.id); + } + } + + /** + * Apply light state requests for all light IDs. + * + * <p>In case of conflict, the session that started earliest wins. + */ + private void invalidateLightStatesLocked() { + final Map<Integer, LightState> states = new HashMap<>(); + for (int i = mSessions.size() - 1; i >= 0; i--) { + SparseArray<LightState> requests = mSessions.get(i).mRequests; + for (int j = 0; j < requests.size(); j++) { + states.put(requests.keyAt(j), requests.valueAt(j)); + } + } + for (int i = 0; i < mLightsById.size(); i++) { + LightImpl light = mLightsById.valueAt(i); + HwLight hwLight = light.getHwLight(); + if (!isSystemLight(hwLight)) { + LightState state = states.get(hwLight.id); + if (state != null) { + light.setColor(state.getColor()); + } else { + light.turnOff(); + } + } + } + } - private final class LightImpl extends Light { + private @Nullable Session getSessionLocked(IBinder token) { + for (int i = 0; i < mSessions.size(); i++) { + if (token.equals(mSessions.get(i).mToken)) { + return mSessions.get(i); + } + } + return null; + } + } + private final class LightImpl extends LogicalLight { private final IBinder mDisplayToken; private final int mSurfaceControlMaximumBrightness; - private LightImpl(Context context, int id) { - mId = id; + private LightImpl(Context context, HwLight hwLight) { + mHwLight = hwLight; mDisplayToken = SurfaceControl.getInternalDisplayToken(); final boolean brightnessSupport = SurfaceControl.getDisplayBrightnessSupport( mDisplayToken); @@ -78,8 +269,8 @@ public class LightsService extends SystemService { synchronized (this) { // LOW_PERSISTENCE cannot be manually set if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) { - Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mId + - ": brightness=0x" + Integer.toHexString(brightness)); + Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mHwLight.id + + ": brightness=0x" + Integer.toHexString(brightness)); return; } // Ideally, we'd like to set the brightness mode through the SF/HWC as well, but @@ -138,7 +329,7 @@ public class LightsService extends SystemService { setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000, BRIGHTNESS_MODE_USER); mColor = 0; - mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS); + mH.postDelayed(this::stopFlashing, onMS); } } } @@ -185,8 +376,10 @@ public class LightsService extends SystemService { if (!mInitialized || color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS || mBrightnessMode != brightnessMode) { - if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" - + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode); + if (DEBUG) { + Slog.v(TAG, "setLight #" + mHwLight.id + ": color=#" + + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode); + } mInitialized = true; mLastColor = mColor; mColor = color; @@ -194,10 +387,31 @@ public class LightsService extends SystemService { mOnMS = onMS; mOffMS = offMS; mBrightnessMode = brightnessMode; - Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x" - + Integer.toHexString(color) + ")"); + setLightUnchecked(color, mode, onMS, offMS, brightnessMode); + } + } + + private void setLightUnchecked(int color, int mode, int onMS, int offMS, + int brightnessMode) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLightState(" + mHwLight.id + ", 0x" + + Integer.toHexString(color) + ")"); + if (mVintfLights != null) { + HwLightState lightState = new HwLightState(); + lightState.color = color; + lightState.flashMode = (byte) mode; + lightState.flashOnMs = onMS; + lightState.flashOffMs = offMS; + lightState.brightnessMode = (byte) brightnessMode; try { - setLight_native(mId, color, mode, onMS, offMS, brightnessMode); + mVintfLights.setLightState(mHwLight.id, lightState); + } catch (RemoteException | UnsupportedOperationException ex) { + Slog.e(TAG, "Failed issuing setLightState", ex); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } else { + try { + setLight_native(mHwLight.id, color, mode, onMS, offMS, brightnessMode); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -208,7 +422,15 @@ public class LightsService extends SystemService { return mVrModeEnabled && mUseLowPersistenceForVR; } - private int mId; + private HwLight getHwLight() { + return mHwLight; + } + + private int getColor() { + return mColor; + } + + private HwLight mHwLight; private int mColor; private int mMode; private int mOnMS; @@ -223,16 +445,59 @@ public class LightsService extends SystemService { } public LightsService(Context context) { + this(context, + ILights.Stub.asInterface( + ServiceManager.getService("android.hardware.light.ILights/default")), + Looper.myLooper()); + } + + @VisibleForTesting + LightsService(Context context, ILights service, Looper looper) { super(context); + mH = new Handler(looper); + mVintfLights = service; + mManagerService = new LightsManagerBinderService(); + populateAvailableLights(context); + } - for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) { - mLights[i] = new LightImpl(context, i); + private void populateAvailableLights(Context context) { + mLights = new LightImpl[LightsManager.LIGHT_ID_COUNT]; + mLightsById = new SparseArray<>(); + + if (mVintfLights != null) { + try { + for (HwLight availableLight : mVintfLights.getLights()) { + LightImpl light = new LightImpl(context, availableLight); + int type = (int) availableLight.type; + if (0 <= type && type < mLights.length && mLights[type] == null) { + mLights[type] = light; + } + mLightsById.put(availableLight.id, light); + } + } catch (RemoteException ex) { + Slog.e(TAG, "Unable to get lights for initialization", ex); + } + } + + // In the case where only the old HAL is available, all lights will be initialized here + for (int i = 0; i < mLights.length; i++) { + if (mLights[i] == null) { + // The ordinal can be anything if there is only 1 light of each type. Set it to 1. + HwLight light = new HwLight(); + light.id = (byte) i; + light.ordinal = 1; + light.type = (byte) i; + + mLights[i] = new LightImpl(context, light); + mLightsById.put(i, mLights[i]); + } } } @Override public void onStart() { publishLocalService(LightsManager.class, mService); + publishBinderService(Context.LIGHTS_SERVICE, mManagerService); } @Override @@ -249,22 +514,25 @@ public class LightsService extends SystemService { private final LightsManager mService = new LightsManager() { @Override - public Light getLight(int id) { - if (0 <= id && id < LIGHT_ID_COUNT) { - return mLights[id]; + public LogicalLight getLight(int lightType) { + if (mLights != null && 0 <= lightType && lightType < mLights.length) { + return mLights[lightType]; } else { return null; } } }; - private Handler mH = new Handler() { - @Override - public void handleMessage(Message msg) { - LightImpl light = (LightImpl)msg.obj; - light.stopFlashing(); - } - }; + /** + * Returns whether a light is system-use-only or should be accessible to + * applications using the {@link android.hardware.lights.LightsManager} API. + */ + private static boolean isSystemLight(HwLight light) { + // LIGHT_ID_COUNT comes from the 2.0 HIDL HAL and only contains system + // lights. Newly added lights will be made available via the + // LightsManager API. + return 0 <= light.type && light.type < LightsManager.LIGHT_ID_COUNT; + } static native void setLight_native(int light, int color, int mode, int onMS, int offMS, int brightnessMode); diff --git a/services/core/java/com/android/server/lights/Light.java b/services/core/java/com/android/server/lights/LogicalLight.java index 998c7c66b504..33dfbb4eea48 100644 --- a/services/core/java/com/android/server/lights/Light.java +++ b/services/core/java/com/android/server/lights/LogicalLight.java @@ -19,9 +19,24 @@ package com.android.server.lights; import android.hardware.light.V2_0.Brightness; import android.hardware.light.V2_0.Flash; -public abstract class Light { +/** + * Allow control over a logical light of a given type. The mapping of logical lights to physical + * lights is HAL implementation-dependent. + */ +public abstract class LogicalLight { + /** + * Keep the light steady on or off. + */ public static final int LIGHT_FLASH_NONE = Flash.NONE; + + /** + * Flash the light at specified rate. + */ public static final int LIGHT_FLASH_TIMED = Flash.TIMED; + + /** + * Flash the light using hardware assist. + */ public static final int LIGHT_FLASH_HARDWARE = Flash.HARDWARE; /** @@ -55,10 +70,33 @@ public abstract class Light { */ public abstract void setBrightnessFloat(float brightness); + /** + * Set the color of a light. + */ public abstract void setColor(int color); + + /** + * Set the color of a light and control flashing. + */ public abstract void setFlashing(int color, int mode, int onMS, int offMS); + + /** + * Pulses the light. + */ public abstract void pulse(); + + /** + * Pulses the light with a specified color for a specified duration. + */ public abstract void pulse(int color, int onMS); + + /** + * Turns off the light. + */ public abstract void turnOff(); + + /** + * Set the VR mode of a display. + */ public abstract void setVrMode(boolean enabled); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6f43952a3f58..7cc67324032a 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -251,8 +251,8 @@ import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; -import com.android.server.lights.Light; import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.ManagedServices.UserProfiles; import com.android.server.pm.PackageManagerService; @@ -413,8 +413,8 @@ public class NotificationManagerService extends SystemService { private final HandlerThread mRankingThread = new HandlerThread("ranker", Process.THREAD_PRIORITY_BACKGROUND); - private Light mNotificationLight; - Light mAttentionLight; + private LogicalLight mNotificationLight; + LogicalLight mAttentionLight; private long[] mFallbackVibrationPattern; private boolean mUseAttentionLight; @@ -1753,7 +1753,7 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - void setLights(Light light) { + void setLights(LogicalLight light) { mNotificationLight = light; mAttentionLight = light; mNotificationPulseEnabled = true; @@ -7875,7 +7875,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord.Light light = ledNotification.getLight(); if (light != null && mNotificationPulseEnabled) { // pulse repeatedly - mNotificationLight.setFlashing(light.color, Light.LIGHT_FLASH_TIMED, + mNotificationLight.setFlashing(light.color, LogicalLight.LIGHT_FLASH_TIMED, light.onMs, light.offMs); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index ed955a260a52..124bbf52561a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -597,6 +597,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.isStagedSessionReady = mStagedSessionReady; info.isStagedSessionFailed = mStagedSessionFailed; info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage); + info.createdMillis = createdMillis; info.updatedMillis = updatedMillis; } return info; diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index ca368694ca92..3f3a13368ffc 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -16,8 +16,8 @@ package com.android.server.power; -import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; @@ -96,8 +96,8 @@ import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; -import com.android.server.lights.Light; import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; @@ -257,7 +257,7 @@ public final class PowerManagerService extends SystemService private WirelessChargerDetector mWirelessChargerDetector; private SettingsObserver mSettingsObserver; private DreamManagerInternal mDreamManager; - private Light mAttentionLight; + private LogicalLight mAttentionLight; private InattentiveSleepWarningController mInattentiveSleepWarningOverlayController; @@ -3347,7 +3347,7 @@ public final class PowerManagerService extends SystemService } private void setAttentionLightInternal(boolean on, int color) { - Light light; + LogicalLight light; synchronized (mLock) { if (!mSystemReady) { return; @@ -3356,7 +3356,7 @@ public final class PowerManagerService extends SystemService } // Control light outside of lock. - light.setFlashing(color, Light.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0); + light.setFlashing(color, LogicalLight.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0); } private void setDozeAfterScreenOffInternal(boolean on) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 81a4c682e809..e82394c5ae27 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -71,6 +71,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; +import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; @@ -1367,52 +1368,10 @@ public class WindowManagerService extends IWindowManager.Stub boolean addToastWindowRequiresToken = false; if (token == null) { - if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { - ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_INPUT_METHOD) { - ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_VOICE_INTERACTION) { - ProtoLog.w(WM_ERROR, - "Attempted to add voice interaction window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_WALLPAPER) { - ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_DREAM) { - ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (rootType == TYPE_QS_DIALOG) { - ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token " - + "%s. Aborting.", attrs.token); + if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type, + rootType, attrs.token, attrs.packageName)) { return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } - if (rootType == TYPE_ACCESSIBILITY_OVERLAY) { - ProtoLog.w(WM_ERROR, - "Attempted to add Accessibility overlay window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - if (type == TYPE_TOAST) { - // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. - if (doesAddToastWindowRequireToken(attrs.packageName, callingUid, - parentWindow)) { - ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token " - + "%s. Aborting.", attrs.token); - return WindowManagerGlobal.ADD_BAD_APP_TOKEN; - } - } final IBinder binder = attrs.token != null ? attrs.token : client.asBinder(); final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -1697,6 +1656,56 @@ public class WindowManagerService extends IWindowManager.Stub return res; } + private boolean unprivilegedAppCanCreateTokenWith(WindowState parentWindow, + int callingUid, int type, int rootType, IBinder tokenForLog, String packageName) { + if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) { + ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_INPUT_METHOD) { + ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_VOICE_INTERACTION) { + ProtoLog.w(WM_ERROR, + "Attempted to add voice interaction window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_WALLPAPER) { + ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_DREAM) { + ProtoLog.w(WM_ERROR, "Attempted to add Dream window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_QS_DIALOG) { + ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (rootType == TYPE_ACCESSIBILITY_OVERLAY) { + ProtoLog.w(WM_ERROR, + "Attempted to add Accessibility overlay window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + if (type == TYPE_TOAST) { + // Apps targeting SDK above N MR1 cannot arbitrary add toast windows. + if (doesAddToastWindowRequireToken(packageName, callingUid, parentWindow)) { + ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token " + + "%s. Aborting.", tokenForLog); + return false; + } + } + return true; + } + /** * Get existing {@link DisplayContent} or create a new one if the display is registered in * DisplayManager. @@ -2501,16 +2510,36 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void addWindowToken(IBinder binder, int type, int displayId) { - if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + addWindowContextToken(binder, type, displayId, null); + } + + @Override + public int addWindowContextToken(IBinder binder, int type, int displayId, String packageName) { + final boolean callerCanManageAppTokens = + checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()"); + if (!callerCanManageAppTokens) { + // TODO(window-context): refactor checkAddPermission to not take attrs. + LayoutParams attrs = new LayoutParams(type); + attrs.packageName = packageName; + final int res = mPolicy.checkAddPermission(attrs, new int[1]); + if (res != ADD_OKAY) { + return res; + } } synchronized (mGlobalLock) { + if (!callerCanManageAppTokens) { + if (!unprivilegedAppCanCreateTokenWith(null, Binder.getCallingUid(), type, type, + null, packageName) || packageName == null) { + throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + } + } + final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */); if (dc == null) { ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add token: %s" + " for non-exiting displayId=%d", binder, displayId); - return; + return WindowManagerGlobal.ADD_INVALID_DISPLAY; } WindowToken token = dc.getWindowToken(binder); @@ -2518,14 +2547,27 @@ public class WindowManagerService extends IWindowManager.Stub ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add binder token: %s" + " for already created window token: %s" + " displayId=%d", binder, token, displayId); - return; + return WindowManagerGlobal.ADD_DUPLICATE_ADD; } + // TODO(window-container): Clean up dead tokens if (type == TYPE_WALLPAPER) { - new WallpaperWindowToken(this, binder, true, dc, - true /* ownerCanManageAppTokens */); + new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens); } else { - new WindowToken(this, binder, type, true, dc, true /* ownerCanManageAppTokens */); + new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens); + } + } + return WindowManagerGlobal.ADD_OKAY; + } + + @Override + public boolean isWindowToken(IBinder binder) { + synchronized (mGlobalLock) { + final WindowToken windowToken = mRoot.getWindowToken(binder); + if (windowToken == null) { + return false; } + // We don't allow activity tokens in WindowContext. TODO(window-context): rename method + return windowToken.asActivityRecord() == null; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 8641059aebd5..43ee97dfa17b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -68,4 +68,11 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public boolean isOrganizationOwnedDeviceWithManagedProfile() { return false; } + + public int getPersonalAppsSuspendedReasons(ComponentName admin) { + return 0; + } + + public void setPersonalAppsSuspended(ComponentName admin, boolean suspended) { + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7830c60806ae..0171582e9d86 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; +import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; @@ -126,6 +127,7 @@ import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.PasswordComplexity; +import android.app.admin.DevicePolicyManager.PersonalAppSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; @@ -254,6 +256,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.telephony.SmsApplication; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; @@ -375,6 +378,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_SECONDARY_LOCK_SCREEN = "secondary-lock-screen"; + private static final String TAG_PERSONAL_APPS_SUSPENDED = "personal-apps-suspended"; + private static final int REQUEST_EXPIRE_PASSWORD = 5571; private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); @@ -445,6 +450,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // A collection of user restrictions that are deprecated and should simply be ignored. private static final Set<String> DEPRECATED_USER_RESTRICTIONS; private static final String AB_DEVICE_KEY = "ro.build.ab_update"; + // Permissions related to location which must not be granted automatically + private static final Set<String> LOCATION_PERMISSIONS; static { SECURE_SETTINGS_WHITELIST = new ArraySet<>(); @@ -489,6 +496,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet( UserManager.DISALLOW_ADD_MANAGED_PROFILE, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); + + LOCATION_PERMISSIONS = Sets.newHashSet( + permission.ACCESS_FINE_LOCATION, + permission.ACCESS_BACKGROUND_LOCATION, + permission.ACCESS_COARSE_LOCATION); } /** @@ -787,6 +799,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { long mPasswordTokenHandle = 0; + // Flag reflecting the current state of the personal apps suspension. This flag should + // only be written AFTER all the needed apps were suspended or unsuspended. + boolean mPersonalAppsSuspended = false; + public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -1016,6 +1032,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages"; private static final String TAG_FACTORY_RESET_PROTECTION_POLICY = "factory_reset_protection_policy"; + private static final String TAG_SUSPEND_PERSONAL_APPS = "suspend-personal-apps"; DeviceAdminInfo info; @@ -1138,6 +1155,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // represented as an empty list. List<String> mCrossProfilePackages = Collections.emptyList(); + // Whether the admin explicitly requires personal apps to be suspended + boolean mSuspendPersonalApps = false; + ActiveAdmin(DeviceAdminInfo _info, boolean parent) { info = _info; isParent = parent; @@ -1347,8 +1367,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName); } if (isLogoutEnabled) { - writeAttributeValueToXml( - out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled); + writeAttributeValueToXml(out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled); } if (startUserSessionMessage != null) { writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage); @@ -1369,6 +1388,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mFactoryResetProtectionPolicy.writeToXml(out); out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY); } + if (mSuspendPersonalApps) { + writeAttributeValueToXml(out, TAG_SUSPEND_PERSONAL_APPS, mSuspendPersonalApps); + } } void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException { @@ -1605,6 +1627,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) { mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml( parser); + } else if (TAG_SUSPEND_PERSONAL_APPS.equals(tag)) { + mSuspendPersonalApps = Boolean.parseBoolean( + parser.getAttributeValue(null, ATTR_VALUE)); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -3411,6 +3436,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.endTag(null, TAG_PROTECTED_PACKAGES); } + if (policy.mPersonalAppsSuspended) { + out.startTag(null, TAG_PERSONAL_APPS_SUSPENDED); + out.attribute(null, ATTR_VALUE, + Boolean.toString(policy.mPersonalAppsSuspended)); + out.endTag(null, TAG_PERSONAL_APPS_SUSPENDED); + } + out.endTag(null, "policies"); out.endDocument(); @@ -3627,6 +3659,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS)); } else if (TAG_PROTECTED_PACKAGES.equals(tag)) { policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME)); + } else if (TAG_PERSONAL_APPS_SUSPENDED.equals(tag)) { + policy.mPersonalAppsSuspended = + Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_VALUE)); } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -3767,6 +3802,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { maybeMigrateToProfileOnOrganizationOwnedDeviceLocked(); } + checkPackageSuspensionOnBoot(); break; case SystemService.PHASE_BOOT_COMPLETED: ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this. @@ -3774,6 +3810,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private void checkPackageSuspensionOnBoot() { + int profileUserId = UserHandle.USER_NULL; + final boolean shouldSuspend; + synchronized (getLockObject()) { + for (final int userId : mOwners.getProfileOwnerKeys()) { + if (mOwners.isProfileOwnerOfOrganizationOwnedDevice(userId)) { + profileUserId = userId; + break; + } + } + + if (profileUserId == UserHandle.USER_NULL) { + shouldSuspend = false; + } else { + shouldSuspend = getProfileOwnerAdminLocked(profileUserId).mSuspendPersonalApps; + } + } + + final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended; + if (suspended != shouldSuspend) { + suspendPersonalAppsInternal(shouldSuspend, UserHandle.USER_SYSTEM); + } + + if (shouldSuspend) { + sendPersonalAppsSuspendedNotification(profileUserId); + } + } + private void onLockSettingsReady() { getUserData(UserHandle.USER_SYSTEM); loadOwners(); @@ -3886,11 +3950,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override void handleUnlockUser(int userId) { startOwnerService(userId, "unlock-user"); + maybeUpdatePersonalAppsSuspendedNotification(userId); } @Override void handleStopUser(int userId) { stopOwnerService(userId, "stop-user"); + maybeUpdatePersonalAppsSuspendedNotification(userId); } private void startOwnerService(int userId, String actionForLog) { @@ -9749,7 +9815,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } AccessibilityManager accessibilityManager = getAccessibilityManagerForUser(userId); enabledServices = accessibilityManager.getEnabledAccessibilityServiceList( - AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + FEEDBACK_ALL_MASK); } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -12390,6 +12456,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { true); } + // Prevent granting location-related permissions without user consent. + if (LOCATION_PERMISSIONS.contains(permission) + && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED + && !isUnattendedManagedKioskUnchecked()) { + callback.sendResult(null); + return; + } + if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { @@ -14981,23 +15055,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - @Override - public boolean isUnattendedManagedKiosk() { - if (!mHasFeature) { - return false; - } - enforceManageUsers(); - long id = mInjector.binderClearCallingIdentity(); + private boolean isUnattendedManagedKioskUnchecked() { try { return isManagedKioskInternal() && getPowerManagerInternal().wasDeviceIdleFor(UNATTENDED_MANAGED_KIOSK_MS); } catch (RemoteException e) { throw new IllegalStateException(e); - } finally { - mInjector.binderRestoreCallingIdentity(id); } } + @Override + public boolean isUnattendedManagedKiosk() { + if (!mHasFeature) { + return false; + } + enforceManageUsers(); + return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked()); + } + /** * Returns whether the device is currently being used as a publicly-accessible dedicated device. * Assumes that feature checks and permission checks have already been performed, and that the @@ -15166,4 +15241,121 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } return mInjector.settingsGlobalGetInt(Settings.Global.COMMON_CRITERIA_MODE, 0) != 0; } + + @Override + public @PersonalAppSuspensionReason int getPersonalAppsSuspendedReasons(ComponentName who) { + synchronized (getLockObject()) { + final ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, + false /* parent */); + // DO shouldn't be able to use this method. + enforceProfileOwnerOfOrganizationOwnedDevice(admin); + if (admin.mSuspendPersonalApps) { + return DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY; + } else { + return DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED; + } + } + } + + @Override + public void setPersonalAppsSuspended(ComponentName who, boolean suspended) { + final int callingUserId = mInjector.userHandleGetCallingUserId(); + synchronized (getLockObject()) { + final ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, + false /* parent */); + // DO shouldn't be able to use this method. + enforceProfileOwnerOfOrganizationOwnedDevice(admin); + if (admin.mSuspendPersonalApps != suspended) { + admin.mSuspendPersonalApps = suspended; + saveSettingsLocked(callingUserId); + } + } + + if (getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended == suspended) { + // Admin request matches current state, nothing to do. + return; + } + + suspendPersonalAppsInternal(suspended, UserHandle.USER_SYSTEM); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (suspended) { + sendPersonalAppsSuspendedNotification(callingUserId); + } else { + clearPersonalAppsSuspendedNotification(callingUserId); + } + }); + } + + private void suspendPersonalAppsInternal(boolean suspended, int userId) { + Slog.i(LOG_TAG, String.format("%s personal apps for user %d", + suspended ? "Suspending" : "Unsuspending", userId)); + mInjector.binderWithCleanCallingIdentity(() -> { + try { + final String[] appsToSuspend = + new PersonalAppsSuspensionHelper(mContext, mInjector.getPackageManager()) + .getPersonalAppsForSuspension(userId); + final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser( + appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId); + if (!ArrayUtils.isEmpty(failedPackages)) { + Slog.wtf(LOG_TAG, String.format("Failed to %s packages: %s", + suspended ? "suspend" : "unsuspend", String.join(",", failedPackages))); + } + } catch (RemoteException re) { + // Shouldn't happen. + Slog.e(LOG_TAG, "Failed talking to the package manager", re); + } + }); + + synchronized (getLockObject()) { + getUserData(userId).mPersonalAppsSuspended = suspended; + saveSettingsLocked(userId); + } + } + + private void maybeUpdatePersonalAppsSuspendedNotification(int profileUserId) { + // TODO(b/147414651): Unless updated, the notification stops working after turning the + // profile off and back on, so it has to be updated more often than necessary. + if (getUserData(UserHandle.USER_SYSTEM).mPersonalAppsSuspended + && getProfileParentId(profileUserId) == UserHandle.USER_SYSTEM) { + sendPersonalAppsSuspendedNotification(profileUserId); + } + } + + private void clearPersonalAppsSuspendedNotification(int userId) { + mInjector.binderWithCleanCallingIdentity(() -> + mInjector.getNotificationManager().cancel( + SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED)); + } + + private void sendPersonalAppsSuspendedNotification(int userId) { + final String profileOwnerPackageName; + synchronized (getLockObject()) { + profileOwnerPackageName = mOwners.getProfileOwnerComponent(userId).getPackageName(); + } + + final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE); + intent.setPackage(profileOwnerPackageName); + + final PendingIntent pendingIntent = mInjector.pendingIntentGetActivityAsUser(mContext, + 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT, null /* options */, + UserHandle.of(userId)); + + final Notification notification = + new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setOngoing(true) + .setContentTitle( + mContext.getString( + R.string.personal_apps_suspended_notification_title)) + .setContentText(mContext.getString( + R.string.personal_apps_suspended_notification_text)) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentIntent(pendingIntent) + .build(); + mInjector.getNotificationManager().notify( + SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java new file mode 100644 index 000000000000..180acc85e5f6 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java @@ -0,0 +1,201 @@ +/* + * 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.server.devicepolicy; + +import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.IBinder; +import android.os.ServiceManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.Slog; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; +import android.view.inputmethod.InputMethodInfo; + +import com.android.internal.R; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Utility class to find what personal apps should be suspended to limit personal device use. + */ +public class PersonalAppsSuspensionHelper { + private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG; + + private final Context mContext; + private final PackageManager mPackageManager; + + public PersonalAppsSuspensionHelper(Context context, PackageManager packageManager) { + mContext = context; + mPackageManager = packageManager; + } + + /** + * @return List of packages that should be suspended to limit personal use. + */ + String[] getPersonalAppsForSuspension(@UserIdInt int userId) { + final List<PackageInfo> installedPackageInfos = + mPackageManager.getInstalledPackagesAsUser(0 /* flags */, userId); + final Set<String> result = new HashSet<>(); + for (final PackageInfo packageInfo : installedPackageInfos) { + final ApplicationInfo info = packageInfo.applicationInfo; + if ((!info.isSystemApp() && !info.isUpdatedSystemApp()) + || hasLauncherIntent(packageInfo.packageName)) { + result.add(packageInfo.packageName); + } + } + result.removeAll(getCriticalPackages()); + result.removeAll(getSystemLauncherPackages()); + result.removeAll(getAccessibilityServices(userId)); + result.removeAll(getInputMethodPackages(userId)); + result.remove(getActiveLauncherPackages(userId)); + result.remove(getDialerPackage(userId)); + result.remove(getSettingsPackageName(userId)); + + Slog.i(LOG_TAG, "Packages subject to suspension: " + String.join(",", result)); + return result.toArray(new String[0]); + } + + private List<String> getSystemLauncherPackages() { + final List<String> result = new ArrayList<>(); + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + final List<ResolveInfo> matchingActivities = + mPackageManager.queryIntentActivities(intent, 0); + for (final ResolveInfo resolveInfo : matchingActivities) { + if (resolveInfo.activityInfo == null + || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) { + Slog.wtf(LOG_TAG, "Could not find package name for launcher app" + resolveInfo); + continue; + } + final String packageName = resolveInfo.activityInfo.packageName; + try { + final ApplicationInfo applicationInfo = + mPackageManager.getApplicationInfo(packageName, 0); + if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) { + Log.d(LOG_TAG, "Not suspending system launcher package: " + packageName); + result.add(packageName); + } + } catch (PackageManager.NameNotFoundException e) { + Slog.e(LOG_TAG, "Could not find application info for launcher app: " + packageName); + } + } + return result; + } + + private List<String> getAccessibilityServices(int userId) { + final List<AccessibilityServiceInfo> accessibilityServiceInfos = + getAccessibilityManagerForUser(userId) + .getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK); + final List<String> result = new ArrayList<>(); + for (final AccessibilityServiceInfo serviceInfo : accessibilityServiceInfos) { + final ComponentName componentName = + ComponentName.unflattenFromString(serviceInfo.getId()); + if (componentName != null) { + final String packageName = componentName.getPackageName(); + Slog.d(LOG_TAG, "Not suspending a11y service: " + packageName); + result.add(packageName); + } + } + return result; + } + + private List<String> getInputMethodPackages(int userId) { + final List<InputMethodInfo> enabledImes = + InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId); + final List<String> result = new ArrayList<>(); + for (final InputMethodInfo info : enabledImes) { + Slog.d(LOG_TAG, "Not suspending IME: " + info.getPackageName()); + result.add(info.getPackageName()); + } + return result; + } + + @Nullable + private String getActiveLauncherPackages(int userId) { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.addCategory(Intent.CATEGORY_DEFAULT); + return getPackageNameForIntent("active launcher", intent, userId); + } + + @Nullable + private String getSettingsPackageName(int userId) { + final Intent intent = new Intent(Settings.ACTION_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + return getPackageNameForIntent("settings", intent, userId); + } + + @Nullable + private String getDialerPackage(int userId) { + final Intent intent = new Intent(Intent.ACTION_DIAL); + intent.addCategory(Intent.CATEGORY_DEFAULT); + return getPackageNameForIntent("dialer", intent, userId); + } + + @Nullable + private String getPackageNameForIntent(String name, Intent intent, int userId) { + final ResolveInfo resolveInfo = + mPackageManager.resolveActivityAsUser(intent, /* flags= */ 0, userId); + if (resolveInfo != null) { + final String packageName = resolveInfo.activityInfo.packageName; + Slog.d(LOG_TAG, "Not suspending " + name + " package: " + packageName); + return packageName; + } + return null; + } + + private List<String> getCriticalPackages() { + final List<String> result = Arrays.asList(mContext.getResources() + .getStringArray(R.array.config_packagesExemptFromSuspension)); + Slog.d(LOG_TAG, "Not suspending critical packages: " + String.join(",", result)); + return result; + } + + private boolean hasLauncherIntent(String packageName) { + final Intent intentToResolve = new Intent(Intent.ACTION_MAIN); + intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); + intentToResolve.setPackage(packageName); + final List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities( + intentToResolve, PackageManager.GET_UNINSTALLED_PACKAGES); + return resolveInfos != null && !resolveInfos.isEmpty(); + } + + private AccessibilityManager getAccessibilityManagerForUser(int userId) { + final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + final IAccessibilityManager service = + iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder); + return new AccessibilityManager(mContext, service, userId); + } +} diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java new file mode 100644 index 000000000000..b0def605db79 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java @@ -0,0 +1,173 @@ +/* + * 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.server.lights; + +import static android.hardware.lights.LightsRequest.Builder; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.light.HwLight; +import android.hardware.light.HwLightState; +import android.hardware.light.ILights; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; +import android.hardware.lights.LightsManager; +import android.os.Looper; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LightsServiceTest { + + private final ILights mHal = new ILights.Stub() { + @Override + public void setLightState(int id, HwLightState state) { + return; + } + + @Override + public HwLight[] getLights() { + return new HwLight[] { + fakeHwLight(101, 3, 1), + fakeHwLight(102, LightsManager.LIGHT_TYPE_MICROPHONE, 4), + fakeHwLight(103, LightsManager.LIGHT_TYPE_MICROPHONE, 3), + fakeHwLight(104, LightsManager.LIGHT_TYPE_MICROPHONE, 1), + fakeHwLight(105, LightsManager.LIGHT_TYPE_MICROPHONE, 2) + }; + } + }; + + private static HwLight fakeHwLight(int id, int type, int ordinal) { + HwLight light = new HwLight(); + light.id = id; + light.type = (byte) type; + light.ordinal = ordinal; + return light; + } + + @Mock + Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testGetLights_filtersSystemLights() { + LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper()); + LightsManager manager = new LightsManager(mContext, service.mManagerService); + + // When lights are listed, only the 4 MICROPHONE lights should be visible. + assertThat(manager.getLights().size()).isEqualTo(4); + } + + @Test + public void testControlMultipleLights() { + LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper()); + LightsManager manager = new LightsManager(mContext, service.mManagerService); + + // When the session requests to turn 3/4 lights on: + LightsManager.LightsSession session = manager.openSession(); + session.setLights(new Builder() + .setLight(manager.getLights().get(0), new LightState(0xf1)) + .setLight(manager.getLights().get(1), new LightState(0xf2)) + .setLight(manager.getLights().get(2), new LightState(0xf3)) + .build()); + + // Then all 3 should turn on. + assertThat(manager.getLightState(manager.getLights().get(0)).getColor()).isEqualTo(0xf1); + assertThat(manager.getLightState(manager.getLights().get(1)).getColor()).isEqualTo(0xf2); + assertThat(manager.getLightState(manager.getLights().get(2)).getColor()).isEqualTo(0xf3); + + // And the 4th should remain off. + assertThat(manager.getLightState(manager.getLights().get(3)).getColor()).isEqualTo(0x00); + } + + @Test + public void testControlLights_onlyEffectiveForLifetimeOfClient() { + LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper()); + LightsManager manager = new LightsManager(mContext, service.mManagerService); + Light micLight = manager.getLights().get(0); + + // The light should begin by being off. + assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0x00000000); + + // When a session commits changes: + LightsManager.LightsSession session = manager.openSession(); + session.setLights(new Builder().setLight(micLight, new LightState(0xff00ff00)).build()); + // Then the light should turn on. + assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xff00ff00); + + // When the session goes away: + session.close(); + // Then the light should turn off. + assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0x00000000); + } + + @Test + public void testControlLights_firstCallerWinsContention() { + LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper()); + LightsManager manager = new LightsManager(mContext, service.mManagerService); + Light micLight = manager.getLights().get(0); + + LightsManager.LightsSession session1 = manager.openSession(); + LightsManager.LightsSession session2 = manager.openSession(); + + // When session1 and session2 both request the same light: + session1.setLights(new Builder().setLight(micLight, new LightState(0xff0000ff)).build()); + session2.setLights(new Builder().setLight(micLight, new LightState(0xffffffff)).build()); + // Then session1 should win because it was created first. + assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xff0000ff); + + // When session1 goes away: + session1.close(); + // Then session2 should have its request go into effect. + assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0xffffffff); + + // When session2 goes away: + session2.close(); + // Then the light should turn off because there are no more sessions. + assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0); + } + + @Test + public void testClearLight() { + LightsService service = new LightsService(mContext, mHal, Looper.getMainLooper()); + LightsManager manager = new LightsManager(mContext, service.mManagerService); + Light micLight = manager.getLights().get(0); + + // When the session turns a light on: + LightsManager.LightsSession session = manager.openSession(); + session.setLights(new Builder().setLight(micLight, new LightState(0xffffffff)).build()); + + // And then the session clears it again: + session.setLights(new Builder().clearLight(micLight).build()); + + // Then the light should turn back off. + assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0); + } +} 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 15327b6e5463..a8674a8f8be4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -309,6 +309,7 @@ public class PackageInstallerSessionTest { actual.getStagedSessionErrorMessage()); assertEquals(expected.isPrepared(), actual.isPrepared()); assertEquals(expected.isCommitted(), actual.isCommitted()); + assertEquals(expected.createdMillis, actual.createdMillis); assertEquals(expected.isSealed(), actual.isSealed()); assertEquals(expected.isMultiPackage(), actual.isMultiPackage()); assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 1e55b1521956..587cfbf062fb 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -74,7 +74,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.IntPair; import com.android.server.UiServiceTestCase; -import com.android.server.lights.Light; +import com.android.server.lights.LogicalLight; import org.junit.Before; import org.junit.Test; @@ -91,7 +91,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { @Mock AudioManager mAudioManager; @Mock Vibrator mVibrator; @Mock android.media.IRingtonePlayer mRingtonePlayer; - @Mock Light mLight; + @Mock LogicalLight mLight; @Mock NotificationManagerService.WorkerHandler mHandler; @Mock 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 9260fbf97b86..93e09dfb3f57 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -153,8 +153,8 @@ import com.android.internal.util.FastXmlSerializer; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiServiceTestCase; -import com.android.server.lights.Light; import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.uri.UriGrantsManagerInternal; @@ -372,7 +372,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { }); when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid); final LightsManager mockLightsManager = mock(LightsManager.class); - when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class)); + when(mockLightsManager.getLight(anyInt())).thenReturn(mock(LogicalLight.class)); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner); diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index cc260ac14147..32ca250b6c74 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -2,7 +2,6 @@ package android.test.mock { public class MockContext extends android.content.Context { - method public android.view.Display getDisplay(); method public int getDisplayId(); } diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index 9d913b9861e5..36074edd187e 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -17,6 +17,7 @@ package android.test.mock; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.IApplicationThread; import android.app.IServiceConnection; @@ -811,6 +812,11 @@ public class MockContext extends Context { } @Override + public @NonNull Context createWindowContext(int type) { + throw new UnsupportedOperationException(); + } + + @Override public boolean isRestricted() { throw new UnsupportedOperationException(); } @@ -821,7 +827,6 @@ public class MockContext extends Context { throw new UnsupportedOperationException(); } - /** @hide */ @Override public Display getDisplay() { throw new UnsupportedOperationException(); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index d13a625963b6..62bb2db69f39 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -88,6 +88,8 @@ interface IWifiManager boolean disableNetwork(int netId, String packageName); + void allowAutojoinGlobal(boolean choice); + void allowAutojoin(int netId, boolean choice); void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin); @@ -179,8 +181,6 @@ interface IWifiManager int getVerboseLoggingLevel(); - void enableWifiConnectivityManager(boolean enabled); - void disableEphemeralNetwork(String SSID, String packageName); void factoryReset(String packageName); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index f6e3ff0ff02c..f1ebf6b50c81 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -4299,6 +4299,23 @@ public class WifiManager { } /** + * Allows the OEM to enable/disable auto-join globally. + * + * @param choice true to allow autojoin, false to disallow autojoin + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void allowAutojoinGlobal(boolean choice) { + try { + mService.allowAutojoinGlobal(choice); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** * Sets the user choice for allowing auto-join to a network. * The updated choice will be made available through the updated config supplied by the * CONFIGURED_NETWORKS_CHANGED broadcast. @@ -4925,18 +4942,6 @@ public class WifiManager { } /** - * Enable/disable WifiConnectivityManager - * @hide - */ - public void enableWifiConnectivityManager(boolean enabled) { - try { - mService.enableWifiConnectivityManager(enabled); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Returns a byte stream representing the data that needs to be backed up to save the * current Wifi state. * This Wifi state can be restored by calling {@link #restoreBackupData(byte[])}. diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index 19d09d1f2a9c..080c6c772f63 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -178,6 +178,11 @@ public class BaseWifiService extends IWifiManager.Stub { } @Override + public void allowAutojoinGlobal(boolean choice) { + throw new UnsupportedOperationException(); + } + + @Override public void allowAutojoin(int netId, boolean choice) { throw new UnsupportedOperationException(); } @@ -404,7 +409,8 @@ public class BaseWifiService extends IWifiManager.Stub { throw new UnsupportedOperationException(); } - @Override + /** @deprecated use {@link #allowAutojoinGlobal(boolean)} instead */ + @Deprecated public void enableWifiConnectivityManager(boolean enabled) { throw new UnsupportedOperationException(); } |