summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt14
-rwxr-xr-xapi/system-current.txt72
-rw-r--r--api/test-current.txt51
-rw-r--r--core/java/android/app/ContextImpl.java78
-rw-r--r--core/java/android/app/SystemServiceRegistry.java8
-rw-r--r--core/java/android/app/WindowContext.java123
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java88
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java4
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl3
-rw-r--r--core/java/android/content/Context.java82
-rw-r--r--core/java/android/content/ContextWrapper.java11
-rw-r--r--core/java/android/content/pm/PackageInstaller.java12
-rw-r--r--core/java/android/hardware/lights/ILightsManager.aidl33
-rw-r--r--core/java/android/hardware/lights/Light.aidl20
-rw-r--r--core/java/android/hardware/lights/Light.java105
-rw-r--r--core/java/android/hardware/lights/LightState.aidl20
-rw-r--r--core/java/android/hardware/lights/LightState.java84
-rw-r--r--core/java/android/hardware/lights/LightsManager.java204
-rw-r--r--core/java/android/hardware/lights/LightsRequest.java95
-rw-r--r--core/java/android/net/NetworkKey.java9
-rw-r--r--core/java/android/net/NetworkScoreManager.java39
-rw-r--r--core/java/android/view/Display.java10
-rw-r--r--core/java/android/view/IWindowManager.aidl14
-rw-r--r--core/java/android/view/ImeFocusController.java45
-rw-r--r--core/java/android/view/ViewRootImpl.java1
-rw-r--r--core/java/android/view/WindowManager.java2
-rw-r--r--core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java42
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java65
-rw-r--r--core/java/com/android/internal/app/ChooserListAdapter.java5
-rw-r--r--core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java15
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java156
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java7
-rw-r--r--core/java/com/android/internal/app/ResolverListController.java3
-rw-r--r--core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java15
-rw-r--r--core/java/com/android/internal/app/ResolverViewPager.java (renamed from core/java/com/android/internal/app/WrapHeightViewPager.java)29
-rw-r--r--core/jni/android_media_AudioSystem.cpp96
-rw-r--r--core/res/AndroidManifest.xml16
-rw-r--r--core/res/res/layout/chooser_grid.xml29
-rw-r--r--core/res/res/layout/resolver_list.xml42
-rw-r--r--core/res/res/layout/resolver_list_per_profile.xml4
-rw-r--r--core/res/res/layout/resolver_list_with_default.xml44
-rw-r--r--core/res/res/values/colors.xml2
-rw-r--r--core/res/res/values/config.xml7
-rw-r--r--core/res/res/values/strings.xml10
-rw-r--r--core/res/res/values/symbols.xml16
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java157
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java35
-rw-r--r--core/tests/coretests/src/com/android/internal/app/MatcherUtils.java51
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java246
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java6
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java38
-rw-r--r--libs/hwui/DisplayListOps.in48
-rw-r--r--libs/hwui/RecordingCanvas.cpp24
-rw-r--r--libs/hwui/RecordingCanvas.h4
-rw-r--r--libs/hwui/pipeline/skia/GLFunctorDrawable.cpp9
-rw-r--r--libs/hwui/pipeline/skia/VkFunctorDrawable.cpp5
-rw-r--r--libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp4
-rw-r--r--media/java/android/media/AudioPlaybackCaptureConfiguration.java48
-rw-r--r--media/java/android/media/AudioSystem.java7
-rw-r--r--media/java/android/media/IAudioService.aidl4
-rw-r--r--media/java/android/media/audiopolicy/AudioMixingRule.java77
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java75
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java8
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java9
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/BatteryService.java6
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java78
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java4
-rw-r--r--services/core/java/com/android/server/lights/LightsManager.java5
-rw-r--r--services/core/java/com/android/server/lights/LightsService.java320
-rw-r--r--services/core/java/com/android/server/lights/LogicalLight.java (renamed from services/core/java/com/android/server/lights/Light.java)40
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java1
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java144
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java216
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java201
-rw-r--r--services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java173
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java4
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java4
-rw-r--r--test-mock/api/test-current.txt1
-rw-r--r--test-mock/src/android/test/mock/MockContext.java7
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl4
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java29
-rw-r--r--wifi/java/com/android/server/wifi/BaseWifiService.java8
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(&params.transform[0]);
+ mat4.getColMajor(&params.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();
}