Merge "Add a new setting that saves if auto-resume timeout is set by user."
diff --git a/core/api/current.txt b/core/api/current.txt
index 5f7e330..cc8b638 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32503,6 +32503,7 @@
field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
field public static final String DISALLOW_ADD_USER = "no_add_user";
+ field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
@@ -32558,6 +32559,7 @@
field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
field public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
field public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+ field public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
field public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
field public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
field public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
@@ -51344,7 +51346,7 @@
method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
- method public void addAudioDescriptionByDefaultStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionByDefaultStateChangeListener);
+ method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
method @ColorInt public int getAccessibilityFocusColor();
@@ -51361,7 +51363,7 @@
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
- method public boolean removeAudioDescriptionByDefaultStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionByDefaultStateChangeListener);
+ method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -51377,8 +51379,8 @@
method public void onAccessibilityStateChanged(boolean);
}
- public static interface AccessibilityManager.AudioDescriptionByDefaultStateChangeListener {
- method public void onAudioDescriptionByDefaultStateChanged(boolean);
+ public static interface AccessibilityManager.AudioDescriptionRequestedChangeListener {
+ method public void onAudioDescriptionRequestedChanged(boolean);
}
public static interface AccessibilityManager.TouchExplorationStateChangeListener {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 0bbc80c..b8ce02e7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -79,7 +79,7 @@
}
public abstract class ContentResolver {
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public final void registerContentObserverAsUser(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver, @NonNull android.os.UserHandle);
+ method @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public final void registerContentObserverAsUser(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver, @NonNull android.os.UserHandle);
}
public abstract class Context {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a298354..dd951b4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -966,6 +966,7 @@
}
public class DevicePolicyManager {
+ method public int checkProvisioningPreCondition(@NonNull String, @NonNull String);
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
@@ -991,6 +992,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle);
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
@@ -1004,6 +1006,21 @@
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+ field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
+ field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
+ field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
+ field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
+ field public static final int CODE_HAS_PAIRED = 8; // 0x8
+ field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
+ field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
+ field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
+ field public static final int CODE_OK = 0; // 0x0
+ field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
+ field public static final int CODE_SYSTEM_USER = 10; // 0xa
+ field public static final int CODE_UNKNOWN_ERROR = -1; // 0xffffffff
+ field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
+ field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
+ field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -5403,6 +5420,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void cancelMuteAwaitConnection(@NonNull android.media.AudioDeviceAttributes) throws java.lang.IllegalStateException;
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
@@ -5416,6 +5434,7 @@
method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getMutingExpectedDevice();
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -5424,7 +5443,9 @@
method public boolean isAudioServerRunning();
method public boolean isHdmiSystemAudioSupported();
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
@@ -5444,6 +5465,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
@@ -5463,6 +5485,15 @@
method public void onAudioServerUp();
}
+ public abstract static class AudioManager.MuteAwaitConnectionCallback {
+ ctor public AudioManager.MuteAwaitConnectionCallback();
+ method public void onMutedUntilConnection(@NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
+ method public void onUnmutedEvent(int, @NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
+ field public static final int EVENT_CANCEL = 3; // 0x3
+ field public static final int EVENT_CONNECTION = 1; // 0x1
+ field public static final int EVENT_TIMEOUT = 2; // 0x2
+ }
+
@Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1014673..aa791aa 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -437,7 +437,6 @@
package android.app.admin {
public class DevicePolicyManager {
- method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId();
method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
@@ -465,22 +464,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
- field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
- field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
- field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
- field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
- field public static final int CODE_HAS_PAIRED = 8; // 0x8
- field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
- field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
- field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
- field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
- field public static final int CODE_OK = 0; // 0x0
- field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
- field public static final int CODE_SYSTEM_USER = 10; // 0xa
- field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
- field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
- field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17
field public static final int OPERATION_CREATE_AND_MANAGE_USER = 5; // 0x5
field public static final int OPERATION_INSTALL_CA_CERT = 24; // 0x18
@@ -1458,6 +1442,7 @@
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
method public void setRampingRingerEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
}
public static final class AudioRecord.MetricsConstants {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 48ceef0..2392c9a 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -626,6 +626,9 @@
* foreground ({@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND}
* and the {@link android.service.quicksettings.TileService} must be exported.
*
+ * Note: the system can choose to auto-deny a request if the user has denied that specific
+ * request (user, ComponentName) enough times before.
+ *
* @param tileServiceComponentName {@link ComponentName} of the
* {@link android.service.quicksettings.TileService} for the request.
* @param tileLabel label of the tile to show to the user.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ee74bb8..ba28283 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2158,13 +2158,24 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
+ * <p>Unknown error code returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CODE_UNKNOWN_ERROR = -1;
+
+ /**
+ * Result code for {@link #checkProvisioningPreCondition}.
+ *
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
* when provisioning is allowed.
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_OK = 0;
/**
@@ -2175,7 +2186,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_HAS_DEVICE_OWNER = 1;
/**
@@ -2186,7 +2197,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_USER_HAS_PROFILE_OWNER = 2;
/**
@@ -2196,7 +2207,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_USER_NOT_RUNNING = 3;
/**
@@ -2207,7 +2218,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_USER_SETUP_COMPLETED = 4;
/**
@@ -2215,7 +2226,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_NONSYSTEM_USER_EXISTS = 5;
/**
@@ -2223,7 +2234,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_ACCOUNTS_NOT_EMPTY = 6;
/**
@@ -2233,7 +2244,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_NOT_SYSTEM_USER = 7;
/**
@@ -2244,7 +2255,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_HAS_PAIRED = 8;
/**
@@ -2256,7 +2267,7 @@
* @see {@link PackageManager#FEATURE_MANAGED_USERS}
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9;
/**
@@ -2268,7 +2279,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_SYSTEM_USER = 10;
/**
@@ -2279,7 +2290,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11;
/**
@@ -2289,7 +2300,6 @@
* @deprecated not used anymore but can't be removed since it's a @TestApi.
**/
@Deprecated
- @TestApi
public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
/**
@@ -2301,7 +2311,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13;
/**
@@ -2323,7 +2333,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15;
/**
@@ -2334,8 +2344,8 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "CODE_" }, value = {
- CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER, CODE_USER_NOT_RUNNING,
- CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
+ CODE_UNKNOWN_ERROR, CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER,
+ CODE_USER_NOT_RUNNING, CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
@@ -11413,9 +11423,9 @@
* @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
* @hide
*/
- @TestApi
+ @SystemApi
public @ProvisioningPreCondition int checkProvisioningPreCondition(
- @Nullable String action, @NonNull String packageName) {
+ @NonNull String action, @NonNull String packageName) {
try {
return mService.checkProvisioningPreCondition(action, packageName);
} catch (RemoteException re) {
@@ -12135,8 +12145,10 @@
*
* @param state to store
* @param userHandle for user
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
public void setUserProvisioningState(@UserProvisioningState int state, int userHandle) {
if (mService != null) {
try {
@@ -12148,6 +12160,34 @@
}
/**
+ * Set the {@link UserProvisioningState} for the supplied user. The supplied user has to be
+ * manged, otherwise it will throw an {@link IllegalStateException}.
+ *
+ * <p> For managed users/profiles/devices, only the following state changes are allowed:
+ * <ul>
+ * <li>{@link #STATE_USER_UNMANAGED} can change to any other state except itself
+ * <li>{@link #STATE_USER_SETUP_INCOMPLETE} and {@link #STATE_USER_SETUP_COMPLETE} can only
+ * change to {@link #STATE_USER_SETUP_FINALIZED}</li>
+ * <li>{@link #STATE_USER_PROFILE_COMPLETE} can only change to
+ * {@link #STATE_USER_PROFILE_FINALIZED}</li>
+ * <li>{@link #STATE_USER_SETUP_FINALIZED} can't be changed to any other state</li>
+ * <li>{@link #STATE_USER_PROFILE_FINALIZED} can only change to
+ * {@link #STATE_USER_UNMANAGED}</li>
+ * </ul>
+ * @param state to store
+ * @param userHandle for user
+ * @throws IllegalStateException if called with an invalid state change.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void setUserProvisioningState(
+ @UserProvisioningState int state, @NonNull UserHandle userHandle) {
+ setUserProvisioningState(state, userHandle.getIdentifier());
+ }
+
+ /**
* Indicates the entity that controls the device. Two users are
* affiliated if the set of ids set by the device owner and the admin of the secondary user.
*
diff --git a/core/java/android/companion/OWNERS b/core/java/android/companion/OWNERS
index 54b35fc..004f66c 100644
--- a/core/java/android/companion/OWNERS
+++ b/core/java/android/companion/OWNERS
@@ -1,4 +1,5 @@
ewol@google.com
evanxinchen@google.com
guojing@google.com
-svetoslavganov@google.com
\ No newline at end of file
+svetoslavganov@google.com
+sergeynv@google.com
\ No newline at end of file
diff --git a/core/java/android/content/AttributionSource.aidl b/core/java/android/content/AttributionSource.aidl
index 10d5c27..7554cb2 100644
--- a/core/java/android/content/AttributionSource.aidl
+++ b/core/java/android/content/AttributionSource.aidl
@@ -16,4 +16,5 @@
package android.content;
+@JavaOnlyStableParcelable
parcelable AttributionSource;
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 1d4d30d..f335ae4 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
@@ -98,6 +99,7 @@
boolean mAbortBroadcast;
@UnsupportedAppUsage
boolean mFinished;
+ String mReceiverClassName;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -219,6 +221,12 @@
* next broadcast will proceed.
*/
public final void finish() {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "PendingResult#finish#ClassName:" + mReceiverClassName,
+ 1);
+ }
+
if (mType == TYPE_COMPONENT) {
final IActivityManager mgr = ActivityManager.getService();
if (QueuedWork.hasPendingWork()) {
@@ -383,6 +391,14 @@
public final PendingResult goAsync() {
PendingResult res = mPendingResult;
mPendingResult = null;
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ res.mReceiverClassName = getClass().getName();
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "BroadcastReceiver#goAsync#ClassName:" + res.mReceiverClassName,
+ 1);
+ }
+
return res;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index adae599..184acb1 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -2684,7 +2684,8 @@
* @hide
* @see #unregisterContentObserver
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ conditional = true)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final void registerContentObserverAsUser(@NonNull Uri uri,
boolean notifyForDescendants,
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 58f20ef..45d6ddd 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1017,12 +1017,12 @@
* This change id restricts treatments that force a given min aspect ratio to activities
* whose orientation is fixed to portrait.
*
- * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+ * This treatment is enabled by default and only takes effect if OVERRIDE_MIN_ASPECT_RATIO is
+ * also enabled.
* @hide
*/
@ChangeId
@Overridable
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2)
@TestApi
public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 43ef33e..28046c5 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -151,6 +151,12 @@
int BIOMETRIC_ERROR_RE_ENROLL = 16;
/**
+ * The privacy setting has been enabled and will block use of the sensor.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18;
+
+ /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index fe43c83..fd46f24 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -69,7 +69,7 @@
BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_ERROR_RE_ENROLL,
- FACE_ERROR_UNKNOWN
+ FACE_ERROR_UNKNOWN,
})
@Retention(RetentionPolicy.SOURCE)
@interface FaceError {}
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index f55bcd3..b989488 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -2,5 +2,6 @@
include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 53484d2..584f3c4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -634,7 +634,7 @@
*/
public static int mapToInternalProcessState(int procState) {
if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
- return ActivityManager.PROCESS_STATE_NONEXISTENT;
+ return Uid.PROCESS_STATE_NONEXISTENT;
} else if (procState == ActivityManager.PROCESS_STATE_TOP) {
return Uid.PROCESS_STATE_TOP;
} else if (ActivityManager.isForegroundService(procState)) {
@@ -911,6 +911,11 @@
* Total number of process states we track.
*/
public static final int NUM_PROCESS_STATE = 7;
+ /**
+ * State of the UID when it has no running processes. It is intentionally out of
+ * bounds 0..NUM_PROCESS_STATE.
+ */
+ public static final int PROCESS_STATE_NONEXISTENT = NUM_PROCESS_STATE;
// Used in dump
static final String[] PROCESS_STATE_NAMES = {
@@ -930,16 +935,6 @@
"C" // CACHED
};
- /**
- * When the process exits one of these states, we need to make sure cpu time in this state
- * is not attributed to any non-critical process states.
- */
- public static final int[] CRITICAL_PROC_STATES = {
- Uid.PROCESS_STATE_TOP,
- Uid.PROCESS_STATE_FOREGROUND_SERVICE,
- Uid.PROCESS_STATE_FOREGROUND
- };
-
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
public abstract Timer getProcessStateTimer(int state);
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index b3416e9..8292f26 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -67,6 +67,9 @@
private static final String SYSTEM_DRIVER_NAME = "system";
private static final String SYSTEM_DRIVER_VERSION_NAME = "";
private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
+ private static final String ANGLE_DRIVER_NAME = "angle";
+ private static final String ANGLE_DRIVER_VERSION_NAME = "";
+ private static final long ANGLE_DRIVER_VERSION_CODE = 0;
// System properties related to updatable graphics drivers.
private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
@@ -134,14 +137,24 @@
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
- setupAngle(context, coreSettings, pm, packageName);
+ boolean useAngle = false;
+ if (setupAngle(context, coreSettings, pm, packageName)) {
+ if (shouldUseAngle(context, coreSettings, packageName)) {
+ useAngle = true;
+ setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+ 0, packageName, getVulkanVersion(pm));
+ }
+ }
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
- setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
- SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0), packageName,
- getVulkanVersion(pm));
+ if (!useAngle) {
+ setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME,
+ SYSTEM_DRIVER_VERSION_CODE,
+ SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0),
+ packageName, getVulkanVersion(pm));
+ }
}
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bc6dbd8..29d4d78 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -327,6 +327,43 @@
"no_sharing_admin_configured_wifi";
/**
+ * Specifies if a user is disallowed from using Wi-Fi Direct.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it prevents all users from using
+ * Wi-Fi Direct.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
+
+ /**
+ * Specifies if a user is disallowed from adding a new Wi-Fi configuration.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it prevents all users from adding
+ * a new Wi-Fi configuration. This does not limit the owner and carrier's ability
+ * to add a new configuration.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
+
+ /**
* Specifies if a user is disallowed from changing the device
* language. The default value is <code>false</code>.
*
@@ -1500,6 +1537,8 @@
DISALLOW_CHANGE_WIFI_STATE,
DISALLOW_WIFI_TETHERING,
DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+ DISALLOW_WIFI_DIRECT,
+ DISALLOW_ADD_WIFI_CONFIG,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index ba0ac82..176a068 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -67,7 +67,8 @@
* include bold, italic, and normal. Values are constants defined
* in {@link Typeface}.
* @param fontWeightAdjustment An integer describing the adjustment to be made to the font
- * weight.
+ * weight. This is added to the value of the current weight returned by
+ * {@link Typeface#getWeight()}.
* @see Configuration#fontWeightAdjustment This is the adjustment in text font weight
* that is used to reflect the current user's preference for increasing font weight.
*/
@@ -123,6 +124,9 @@
/**
* Returns the font weight adjustment specified by this span.
+ * <p>
+ * This can be {@link Configuration#FONT_WEIGHT_ADJUSTMENT_UNDEFINED}. This is added to the
+ * value of the current weight returned by {@link Typeface#getWeight()}.
*/
public int getFontWeightAdjustment() {
return mFontWeightAdjustment;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ba436e1..3c0597c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -948,9 +948,10 @@
// When the listener is updated, we will get at least a single position update call so we can
// guarantee any changes we post will be applied.
private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight,
- @Nullable Transaction geometryTransaction) {
+ Transaction geometryTransaction) {
if (mPositionListener != null) {
mRenderNode.removePositionUpdateListener(mPositionListener);
+ geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction);
}
mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight,
geometryTransaction);
@@ -958,7 +959,8 @@
}
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
- boolean creating, boolean sizeChanged, boolean hintChanged) {
+ boolean creating, boolean sizeChanged, boolean hintChanged,
+ Transaction geometryTransaction) {
boolean realSizeChanged = false;
mSurfaceLock.lock();
@@ -996,10 +998,6 @@
mSurfaceAlpha = alpha;
}
- // While creating the surface, we will set it's initial
- // geometry. Outside of that though, we should generally
- // leave it to the RenderThread.
- Transaction geometryTransaction = new Transaction();
geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
if ((sizeChanged || hintChanged) && !creating) {
setBufferSize(geometryTransaction);
@@ -1022,20 +1020,18 @@
mSurfaceHeight);
}
- boolean applyChangesOnRenderThread =
- sizeChanged && !creating && isHardwareAccelerated();
if (isHardwareAccelerated()) {
// This will consume the passed in transaction and the transaction will be
// applied on a render worker thread.
replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight,
- applyChangesOnRenderThread ? geometryTransaction : null);
+ geometryTransaction);
}
if (DEBUG_POSITION) {
Log.d(TAG, String.format(
- "%d updateSurfacePosition %s"
+ "%d performSurfaceTransaction %s "
+ "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
System.identityHashCode(this),
- applyChangesOnRenderThread ? "RenderWorker" : "UiThread",
+ isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
mScreenRect.left, mScreenRect.top, mScreenRect.right,
mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
}
@@ -1147,12 +1143,14 @@
final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
+ // Collect all geometry changes and apply these changes on the RenderThread worker
+ // via the RenderNode.PositionUpdateListener.
+ final Transaction geometryTransaction = new Transaction();
if (creating) {
updateOpaqueFlag();
final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
if (mUseBlastAdapter) {
- createBlastSurfaceControls(viewRoot, name);
+ createBlastSurfaceControls(viewRoot, name, geometryTransaction);
} else {
mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
}
@@ -1161,7 +1159,7 @@
}
final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
- translator, creating, sizeChanged, hintChanged);
+ translator, creating, sizeChanged, hintChanged, geometryTransaction);
final boolean redrawNeeded = sizeChanged || creating || hintChanged
|| (mVisible && !mDrawFinished);
@@ -1335,7 +1333,8 @@
// is still alive, the old buffers will continue to be presented until replaced by buffers from
// the new adapter. This means we do not need to track the old surface control and destroy it
// after the client has drawn to avoid any flickers.
- private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+ private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name,
+ Transaction geometryTransaction) {
if (mSurfaceControl == null) {
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
@@ -1376,8 +1375,9 @@
}
mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
- mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight, mFormat);
+ mBlastBufferQueue = new BLASTBufferQueue(name);
+ mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat,
+ geometryTransaction);
}
private void onDrawFinished(Transaction t) {
@@ -1558,6 +1558,10 @@
applyOrMergeTransaction(mRtTransaction, frameNumber);
}
}
+
+ public Transaction getTransaction() {
+ return mPositionChangedTransaction;
+ }
}
private SurfaceViewPositionUpdateListener mPositionListener = null;
@@ -1651,6 +1655,11 @@
@Override
public void setFixedSize(int width, int height) {
if (mRequestedWidth != width || mRequestedHeight != height) {
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format("%d setFixedSize %dx%d -> %dx%d",
+ System.identityHashCode(this), mRequestedWidth, mRequestedHeight, width,
+ height));
+ }
mRequestedWidth = width;
mRequestedHeight = height;
requestLayout();
@@ -1660,6 +1669,10 @@
@Override
public void setSizeFromLayout() {
if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format("%d setSizeFromLayout was %dx%d",
+ System.identityHashCode(this), mRequestedWidth, mRequestedHeight));
+ }
mRequestedWidth = mRequestedHeight = -1;
requestLayout();
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 078b767..7a33507 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -273,8 +273,8 @@
private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
mServicesStateChangeListeners = new ArrayMap<>();
- private final ArrayMap<AudioDescriptionByDefaultStateChangeListener, Executor>
- mAudioDescriptionByDefaultStateChangeListeners = new ArrayMap<>();
+ private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
+ mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
/**
* Map from a view's accessibility id to the list of request preparers set for that view
@@ -359,15 +359,15 @@
* Listener for the audio description by default state. To listen for
* changes to the audio description by default state on the device,
* implement this interface and register it with the system by calling
- * {@link #addAudioDescriptionByDefaultStateChangeListener}.
+ * {@link #addAudioDescriptionRequestedChangeListener}.
*/
- public interface AudioDescriptionByDefaultStateChangeListener {
+ public interface AudioDescriptionRequestedChangeListener {
/**
* Called when the audio description enabled state changes.
*
* @param enabled Whether audio description by default is enabled.
*/
- void onAudioDescriptionByDefaultStateChanged(boolean enabled);
+ void onAudioDescriptionRequestedChanged(boolean enabled);
}
/**
@@ -1177,31 +1177,31 @@
}
/**
- * Registers a {@link AudioDescriptionByDefaultStateChangeListener}
+ * Registers a {@link AudioDescriptionRequestedChangeListener}
* for changes in the audio description by default state of the system.
* The value could be read via {@link #isAudioDescriptionRequested}.
*
* @param executor The executor on which the listener should be called back.
* @param listener The listener.
*/
- public void addAudioDescriptionByDefaultStateChangeListener(
+ public void addAudioDescriptionRequestedChangeListener(
@NonNull Executor executor,
- @NonNull AudioDescriptionByDefaultStateChangeListener listener) {
+ @NonNull AudioDescriptionRequestedChangeListener listener) {
synchronized (mLock) {
- mAudioDescriptionByDefaultStateChangeListeners.put(listener, executor);
+ mAudioDescriptionRequestedChangeListeners.put(listener, executor);
}
}
/**
- * Unregisters a {@link AudioDescriptionByDefaultStateChangeListener}.
+ * Unregisters a {@link AudioDescriptionRequestedChangeListener}.
*
* @param listener The listener.
* @return True if listener was previously registered.
*/
- public boolean removeAudioDescriptionByDefaultStateChangeListener(
- @NonNull AudioDescriptionByDefaultStateChangeListener listener) {
+ public boolean removeAudioDescriptionRequestedChangeListener(
+ @NonNull AudioDescriptionRequestedChangeListener listener) {
synchronized (mLock) {
- return (mAudioDescriptionByDefaultStateChangeListeners.remove(listener) != null);
+ return (mAudioDescriptionRequestedChangeListeners.remove(listener) != null);
}
}
@@ -1752,7 +1752,7 @@
* </p>
* <p>
* Add listener to detect the state change via
- * {@link #addAudioDescriptionByDefaultStateChangeListener}
+ * {@link #addAudioDescriptionRequestedChangeListener}
* </p>
* @return {@code true} if the audio description is enabled, {@code false} otherwise.
*/
@@ -1865,20 +1865,20 @@
*/
private void notifyAudioDescriptionbyDefaultStateChanged() {
final boolean isAudioDescriptionByDefaultRequested;
- final ArrayMap<AudioDescriptionByDefaultStateChangeListener, Executor> listeners;
+ final ArrayMap<AudioDescriptionRequestedChangeListener, Executor> listeners;
synchronized (mLock) {
- if (mAudioDescriptionByDefaultStateChangeListeners.isEmpty()) {
+ if (mAudioDescriptionRequestedChangeListeners.isEmpty()) {
return;
}
isAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
- listeners = new ArrayMap<>(mAudioDescriptionByDefaultStateChangeListeners);
+ listeners = new ArrayMap<>(mAudioDescriptionRequestedChangeListeners);
}
final int numListeners = listeners.size();
for (int i = 0; i < numListeners; i++) {
- final AudioDescriptionByDefaultStateChangeListener listener = listeners.keyAt(i);
+ final AudioDescriptionRequestedChangeListener listener = listeners.keyAt(i);
listeners.valueAt(i).execute(() ->
- listener.onAudioDescriptionByDefaultStateChanged(
+ listener.onAudioDescriptionRequestedChanged(
isAudioDescriptionByDefaultRequested));
}
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 1ad0452..4399207 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -44,4 +44,10 @@
* Unregisters remote animations per transition type for the organizer.
*/
void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
+
+ /**
+ * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * only occupies a portion of Task bounds.
+ */
+ boolean isActivityEmbedded(in IBinder activityToken);
}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 7e7d370..9c2fde0 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -216,4 +216,17 @@
return null;
}
}
+
+ /**
+ * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * only occupies a portion of Task bounds.
+ * @hide
+ */
+ public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
+ try {
+ return getController().isActivityEmbedded(activityToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 359c382..52122ee 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2736,7 +2736,7 @@
}
@Override
- public void onListRebuilt(ResolverListAdapter listAdapter) {
+ public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
setupScrollListener();
maybeSetupGlobalLayoutListener();
@@ -2756,15 +2756,20 @@
chooserListAdapter.updateAlphabeticalList();
}
+ if (rebuildComplete) {
+ getChooserActivityLogger().logSharesheetAppLoadComplete();
+ maybeQueryAdditionalPostProcessingTargets(chooserListAdapter);
+ }
+ }
+
+ private void maybeQueryAdditionalPostProcessingTargets(ChooserListAdapter chooserListAdapter) {
// don't support direct share on low ram devices
if (ActivityManager.isLowRamDeviceStatic()) {
- getChooserActivityLogger().logSharesheetAppLoadComplete();
return;
}
// no need to query direct share for work profile when its locked or disabled
if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
- getChooserActivityLogger().logSharesheetAppLoadComplete();
return;
}
@@ -2775,8 +2780,6 @@
queryDirectShareTargets(chooserListAdapter, false);
}
-
- getChooserActivityLogger().logSharesheetAppLoadComplete();
}
@VisibleForTesting
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index fd8637a..b273f6d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1159,11 +1159,11 @@
if (doPostProcessing) {
maybeCreateHeader(listAdapter);
resetButtonBar();
- onListRebuilt(listAdapter);
+ onListRebuilt(listAdapter, rebuildCompleted);
}
}
- protected void onListRebuilt(ResolverListAdapter listAdapter) {
+ protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
final ItemClickListener listener = new ItemClickListener();
setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
if (shouldShowTabs() && isIntentPicker()) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aba43d8..c2224b4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
@@ -241,32 +242,16 @@
MeasuredEnergyStats.POWER_BUCKET_CPU,
};
+ // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate
+ // Uid.PROCESS_STATE_NONEXISTENT, which is outside the range of legitimate proc states.
+ private static final int PROC_STATE_TIME_COUNTER_STATE_COUNT = NUM_PROCESS_STATE + 1;
+
@GuardedBy("this")
public boolean mPerProcStateCpuTimesAvailable = true;
- /**
- * When per process state cpu times tracking is off, cpu times in KernelSingleUidTimeReader are
- * not updated. So, when the setting is turned on later, we would end up with huge cpu time
- * deltas. This flag tracks the case where tracking is turned on from off so that we won't
- * end up attributing the huge deltas to wrong buckets.
- */
- @GuardedBy("this")
- private boolean mIsPerProcessStateCpuDataStale;
-
- /**
- * Uids for which per-procstate cpu times need to be updated.
- *
- * Contains uid -> procState mappings.
- */
- @GuardedBy("this")
- @VisibleForTesting
- protected final SparseIntArray mPendingUids = new SparseIntArray();
-
@GuardedBy("this")
private long mNumSingleUidCpuTimeReads;
@GuardedBy("this")
- private long mNumBatchedSingleUidCpuTimeReads;
- @GuardedBy("this")
private long mCpuTimeReadsTrackingStartTimeMs = SystemClock.uptimeMillis();
@GuardedBy("this")
private int mNumUidsRemoved;
@@ -443,88 +428,42 @@
}
/**
- * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+ * Update per-freq cpu times for the supplied UID.
*/
- public void updateProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
- final SparseIntArray uidStates;
- synchronized (BatteryStatsImpl.this) {
- if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
- return;
- }
- if(!initKernelSingleUidTimeReaderLocked()) {
- return;
- }
- // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
- // compute deltas since it might result in mis-attributing cpu times to wrong states.
- if (mIsPerProcessStateCpuDataStale) {
- mPendingUids.clear();
- return;
- }
-
- if (mPendingUids.size() == 0) {
- return;
- }
- uidStates = mPendingUids.clone();
- mPendingUids.clear();
+ @GuardedBy("this")
+ @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
+ @VisibleForTesting
+ public void updateProcStateCpuTimesLocked(int uid, long timestampMs) {
+ if (!initKernelSingleUidTimeReaderLocked()) {
+ return;
}
- final long timestampMs = mClock.elapsedRealtime();
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer = null;
- for (int i = uidStates.size() - 1; i >= 0; --i) {
- final int uid = uidStates.keyAt(i);
- final int procState = uidStates.valueAt(i);
- final int[] isolatedUids;
- final LongArrayMultiStateCounter[] isolatedUidTimeInFreqCounters;
- final Uid u;
- synchronized (BatteryStatsImpl.this) {
- // It's possible that uid no longer exists and any internal references have
- // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
- // creating an UidStats object if it doesn't already exist.
- u = getAvailableUidStatsLocked(uid);
- if (u == null) {
- continue;
- }
- if (u.mChildUids == null) {
- isolatedUids = null;
- isolatedUidTimeInFreqCounters = null;
- } else {
- int childUidCount = u.mChildUids.size();
- isolatedUids = new int[childUidCount];
- isolatedUidTimeInFreqCounters = new LongArrayMultiStateCounter[childUidCount];
- for (int j = childUidCount - 1; j >= 0; --j) {
- isolatedUids[j] = u.mChildUids.keyAt(j);
- isolatedUidTimeInFreqCounters[j] =
- u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
- if (deltaContainer == null && isolatedUidTimeInFreqCounters[j] != null) {
- deltaContainer = getCpuTimeInFreqContainer();
- }
- }
+
+ final Uid u = getUidStatsLocked(uid);
+
+ mNumSingleUidCpuTimeReads++;
+
+ LongArrayMultiStateCounter onBatteryCounter =
+ u.getProcStateTimeCounter().getCounter();
+ LongArrayMultiStateCounter onBatteryScreenOffCounter =
+ u.getProcStateScreenOffTimeCounter().getCounter();
+
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
+
+ if (u.mChildUids != null) {
+ LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+ getCpuTimeInFreqContainer();
+ int childUidCount = u.mChildUids.size();
+ for (int j = childUidCount - 1; j >= 0; --j) {
+ LongArrayMultiStateCounter cpuTimeInFreqCounter =
+ u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+ if (cpuTimeInFreqCounter != null) {
+ mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
+ cpuTimeInFreqCounter, timestampMs, deltaContainer);
+ onBatteryCounter.addCounts(deltaContainer);
+ onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
-
- LongArrayMultiStateCounter onBatteryCounter =
- u.getProcStateTimeCounter().getCounter();
- LongArrayMultiStateCounter onBatteryScreenOffCounter =
- u.getProcStateScreenOffTimeCounter().getCounter();
-
- onBatteryCounter.setState(procState, timestampMs);
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-
- onBatteryScreenOffCounter.setState(procState, timestampMs);
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
-
- if (isolatedUids != null) {
- for (int j = isolatedUids.length - 1; j >= 0; --j) {
- if (isolatedUidTimeInFreqCounters[j] != null) {
- mKernelSingleUidTimeReader.addDelta(isolatedUids[j],
- isolatedUidTimeInFreqCounters[j], timestampMs, deltaContainer);
- onBatteryCounter.addCounts(deltaContainer);
- onBatteryScreenOffCounter.addCounts(deltaContainer);
- }
- }
- }
-
- onBatteryCounter.setState(u.mProcessState, timestampMs);
- onBatteryScreenOffCounter.setState(u.mProcessState, timestampMs);
}
}
@@ -542,24 +481,17 @@
}
}
- public void copyFromAllUidsCpuTimes() {
- synchronized (BatteryStatsImpl.this) {
- copyFromAllUidsCpuTimes(
- mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning());
- }
- }
-
/**
* When the battery/screen state changes, we don't attribute the cpu times to any process
- * but we still need to snapshots of all uids to get correct deltas later on. Since we
- * already read this data for updating per-freq cpu times, we can use the same data for
- * per-procstate cpu times.
+ * but we still need to take snapshots of all uids to get correct deltas later on.
*/
- public void copyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
+ @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
+ public void updateCpuTimesForAllUids() {
synchronized (BatteryStatsImpl.this) {
- if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+ if (!trackPerProcStateCpuTimes()) {
return;
}
+
if(!initKernelSingleUidTimeReaderLocked()) {
return;
}
@@ -567,14 +499,6 @@
// TODO(b/197162116): just get a list of UIDs
final SparseArray<long[]> allUidCpuFreqTimesMs =
mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs();
- // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
- // compute deltas since it might result in mis-attributing cpu times to wrong states.
- if (mIsPerProcessStateCpuDataStale) {
- mKernelSingleUidTimeReader.setAllUidsCpuTimesMs(allUidCpuFreqTimesMs);
- mIsPerProcessStateCpuDataStale = false;
- mPendingUids.clear();
- return;
- }
for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
final int uid = allUidCpuFreqTimesMs.keyAt(i);
final int parentUid = mapUid(uid);
@@ -583,16 +507,8 @@
continue;
}
- final int procState;
- final int idx = mPendingUids.indexOfKey(uid);
- if (idx >= 0) {
- procState = mPendingUids.valueAt(idx);
- mPendingUids.removeAt(idx);
- } else {
- procState = u.mProcessState;
- }
-
- if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ final int procState = u.mProcessState;
+ if (procState == Uid.PROCESS_STATE_NONEXISTENT) {
continue;
}
@@ -602,27 +518,19 @@
final LongArrayMultiStateCounter onBatteryScreenOffCounter =
u.getProcStateScreenOffTimeCounter().getCounter();
- onBatteryCounter.setState(procState, timestampMs);
if (uid == parentUid) {
mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
- }
-
- onBatteryScreenOffCounter.setState(procState, timestampMs);
- if (uid == parentUid) {
mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter,
timestampMs);
- }
-
- if (u.mChildUids != null) {
- for (int j = u.mChildUids.size() - 1; j >= 0; --j) {
- final LongArrayMultiStateCounter counter =
- u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+ } else {
+ Uid.ChildUid childUid = u.getChildUid(uid);
+ if (childUid != null) {
+ final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
if (counter != null) {
- final int isolatedUid = u.mChildUids.keyAt(j);
final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
getCpuTimeInFreqContainer();
- mKernelSingleUidTimeReader.addDelta(isolatedUid,
- counter, timestampMs, deltaContainer);
+ mKernelSingleUidTimeReader.addDelta(uid, counter, timestampMs,
+ deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
@@ -689,9 +597,6 @@
Future<?> scheduleSync(String reason, int flags);
Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
- Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff,
- long delayMillis);
- Future<?> scheduleCopyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
Future<?> scheduleCpuSyncDueToSettingChange();
/**
* Schedule a sync because of a screen state change.
@@ -1839,10 +1744,6 @@
return mCounter.getStateCount();
}
- public void setTrackingEnabled(boolean enabled, long timestampMs) {
- mCounter.setEnabled(enabled && mTimeBase.isRunning(), timestampMs);
- }
-
private void setState(@BatteryConsumer.ProcessState int processState,
long elapsedRealtimeMs) {
mCounter.setState(processState, elapsedRealtimeMs);
@@ -8073,7 +7974,7 @@
Counter mBluetoothScanResultCounter;
Counter mBluetoothScanResultBgCounter;
- int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ int mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
StopwatchTimer[] mProcessStateTimer;
boolean mInForegroundService = false;
@@ -8415,6 +8316,11 @@
mChildUids.remove(idx);
}
+ @GuardedBy("mBsi")
+ ChildUid getChildUid(int childUid) {
+ return mChildUids == null ? null : mChildUids.get(childUid);
+ }
+
private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
if (cpuTimesMs == null) {
return null;
@@ -8432,6 +8338,7 @@
return null;
}
+ @GuardedBy("mBsi")
private void ensureMultiStateCounters() {
if (mProcStateTimeMs != null) {
return;
@@ -8440,31 +8347,26 @@
final long timestampMs = mBsi.mClock.elapsedRealtime();
mProcStateTimeMs =
new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
- NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+ PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+ timestampMs);
mProcStateScreenOffTimeMs =
new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase,
- NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+ PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+ timestampMs);
}
+ @GuardedBy("mBsi")
private TimeInFreqMultiStateCounter getProcStateTimeCounter() {
ensureMultiStateCounters();
return mProcStateTimeMs;
}
+ @GuardedBy("mBsi")
private TimeInFreqMultiStateCounter getProcStateScreenOffTimeCounter() {
ensureMultiStateCounters();
return mProcStateScreenOffTimeMs;
}
- private void setProcStateTimesTrackingEnabled(boolean enabled, long timestampMs) {
- if (mProcStateTimeMs != null) {
- mProcStateTimeMs.setTrackingEnabled(enabled, timestampMs);
- }
- if (mProcStateScreenOffTimeMs != null) {
- mProcStateScreenOffTimeMs.setTrackingEnabled(enabled, timestampMs);
- }
- }
-
@Override
public Timer getAggregatedPartialWakelockTimer() {
return mAggregatedPartialWakelockTimer;
@@ -8774,6 +8676,7 @@
processState);
}
+ @GuardedBy("mBsi")
@Override
public long getGnssMeasuredBatteryConsumptionUC() {
return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
@@ -9553,7 +9456,7 @@
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
active |= !resetIfNotNull(mProcessStateTimer[i], false, realtimeUs);
}
- active |= (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT);
+ active |= (mProcessState != Uid.PROCESS_STATE_NONEXISTENT);
}
if (mVibratorOnTimer != null) {
if (mVibratorOnTimer.reset(false, realtimeUs)) {
@@ -10270,7 +10173,7 @@
} else {
mBluetoothScanResultBgCounter = null;
}
- mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
makeProcessState(i, in);
@@ -10360,7 +10263,7 @@
// Read the object from the Parcel, whether it's usable or not
TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
mBsi.mOnBatteryTimeBase, in, timestampMs);
- if (stateCount == NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
mProcStateTimeMs = counter;
}
} else {
@@ -10373,7 +10276,7 @@
TimeInFreqMultiStateCounter counter =
new TimeInFreqMultiStateCounter(
mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
- if (stateCount == NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
mProcStateScreenOffTimeMs = counter;
}
} else {
@@ -11244,12 +11147,6 @@
}
@GuardedBy("mBsi")
- public void updateUidProcessStateLocked(int procState) {
- updateUidProcessStateLocked(procState,
- mBsi.mClock.elapsedRealtime(), mBsi.mClock.uptimeMillis());
- }
-
- @GuardedBy("mBsi")
public void updateUidProcessStateLocked(int procState,
long elapsedRealtimeMs, long uptimeMs) {
int uidRunningState;
@@ -11263,40 +11160,35 @@
}
if (mProcessState != uidRunningState) {
- if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ if (mProcessState != Uid.PROCESS_STATE_NONEXISTENT) {
mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
-
- if (mBsi.trackPerProcStateCpuTimes()) {
- if (mBsi.mPendingUids.size() == 0) {
- mBsi.mExternalSync.scheduleReadProcStateCpuTimes(
- mBsi.mOnBatteryTimeBase.isRunning(),
- mBsi.mOnBatteryScreenOffTimeBase.isRunning(),
- mBsi.mConstants.PROC_STATE_CPU_TIMES_READ_DELAY_MS);
- mBsi.mNumSingleUidCpuTimeReads++;
- } else {
- mBsi.mNumBatchedSingleUidCpuTimeReads++;
- }
- if (mBsi.mPendingUids.indexOfKey(mUid) < 0
- || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
- mBsi.mPendingUids.put(mUid, mProcessState);
- }
- } else {
- mBsi.mPendingUids.clear();
- }
}
- mProcessState = uidRunningState;
- if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ if (uidRunningState != Uid.PROCESS_STATE_NONEXISTENT) {
if (mProcessStateTimer[uidRunningState] == null) {
makeProcessState(uidRunningState, null);
}
mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
}
+ if (mBsi.trackPerProcStateCpuTimes()) {
+ mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs);
+
+ LongArrayMultiStateCounter onBatteryCounter =
+ getProcStateTimeCounter().getCounter();
+ LongArrayMultiStateCounter onBatteryScreenOffCounter =
+ getProcStateScreenOffTimeCounter().getCounter();
+
+ onBatteryCounter.setState(uidRunningState, elapsedRealtimeMs);
+ onBatteryScreenOffCounter.setState(uidRunningState, elapsedRealtimeMs);
+ }
+
+ mProcessState = uidRunningState;
+
updateOnBatteryBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
updateOnBatteryScreenOffBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
final int batteryConsumerProcessState =
- mapUidProcessStateToBatteryConsumerProcessState(mProcessState);
+ mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedRealtimeMs);
final MeasuredEnergyStats energyStats =
@@ -11318,7 +11210,7 @@
/** Whether to consider Uid to be in the background for background timebase purposes. */
public boolean isInBackground() {
- // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+ // Note that PROCESS_STATE_CACHED and Uid.PROCESS_STATE_NONEXISTENT is
// also considered to be 'background' for our purposes, because it's not foreground.
return mProcessState >= PROCESS_STATE_BACKGROUND;
}
@@ -15660,7 +15552,7 @@
@GuardedBy("this")
public boolean trackPerProcStateCpuTimes() {
- return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
+ return mCpuUidFreqTimeReader.isFastCpuTimesReader();
}
@GuardedBy("this")
@@ -15747,8 +15639,6 @@
@VisibleForTesting
public final class Constants extends ContentObserver {
- public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE
- = "track_cpu_times_by_proc_state";
public static final String KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME
= "track_cpu_active_cluster_time";
public static final String KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
@@ -15766,9 +15656,7 @@
public static final String KEY_BATTERY_CHARGED_DELAY_MS =
"battery_charged_delay_ms";
- private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false;
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
- private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
@@ -15779,9 +15667,7 @@
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
- public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
- public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
/* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
* update when startObserving. */
public long KERNEL_UID_READERS_THROTTLE_TIME;
@@ -15843,14 +15729,8 @@
Slog.e(TAG, "Bad batterystats settings", e);
}
- updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE,
- mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE,
- DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
TRACK_CPU_ACTIVE_CLUSTER_TIME = mParser.getBoolean(
KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME, DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME);
- updateProcStateCpuTimesReadDelayMs(PROC_STATE_CPU_TIMES_READ_DELAY_MS,
- mParser.getLong(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS,
- DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
updateKernelUidReadersThrottleTime(KERNEL_UID_READERS_THROTTLE_TIME,
mParser.getLong(KEY_KERNEL_UID_READERS_THROTTLE_TIME,
DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME));
@@ -15887,33 +15767,6 @@
DEFAULT_BATTERY_CHARGED_DELAY_MS);
}
- @GuardedBy("BatteryStatsImpl.this")
- private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
- TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
- if (isEnabled && !wasEnabled) {
- mIsPerProcessStateCpuDataStale = true;
- mExternalSync.scheduleCpuSyncDueToSettingChange();
-
- mNumSingleUidCpuTimeReads = 0;
- mNumBatchedSingleUidCpuTimeReads = 0;
- mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
- }
- final long timestampMs = mClock.elapsedRealtime();
- for (int i = mUidStats.size() - 1; i >= 0; i--) {
- mUidStats.valueAt(i).setProcStateTimesTrackingEnabled(isEnabled, timestampMs);
- }
- }
-
- @GuardedBy("BatteryStatsImpl.this")
- private void updateProcStateCpuTimesReadDelayMs(long oldDelayMillis, long newDelayMillis) {
- PROC_STATE_CPU_TIMES_READ_DELAY_MS = newDelayMillis;
- if (oldDelayMillis != newDelayMillis) {
- mNumSingleUidCpuTimeReads = 0;
- mNumBatchedSingleUidCpuTimeReads = 0;
- mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
- }
- }
-
private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
if (oldTimeMs != newTimeMs) {
@@ -15932,12 +15785,8 @@
}
public void dumpLocked(PrintWriter pw) {
- pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
- pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
pw.print(KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME); pw.print("=");
pw.println(TRACK_CPU_ACTIVE_CLUSTER_TIME);
- pw.print(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS); pw.print("=");
- pw.println(PROC_STATE_CPU_TIMES_READ_DELAY_MS);
pw.print(KEY_KERNEL_UID_READERS_THROTTLE_TIME); pw.print("=");
pw.println(KERNEL_UID_READERS_THROTTLE_TIME);
pw.print(KEY_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS); pw.print("=");
@@ -16606,8 +16455,8 @@
if (in.readInt() != 0) {
u.createBluetoothScanResultBgCounterLocked().readSummaryFromParcelLocked(in);
}
- u.mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+ u.mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
+ for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
u.makeProcessState(i, null);
u.mProcessStateTimer[i].readSummaryFromParcelLocked(in);
@@ -16699,7 +16548,7 @@
// Read the object from the Parcel, whether it's usable or not
TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
mOnBatteryTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == Uid.NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
u.mProcStateTimeMs = counter;
}
}
@@ -16714,7 +16563,7 @@
TimeInFreqMultiStateCounter counter =
new TimeInFreqMultiStateCounter(
mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == Uid.NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
u.mProcStateScreenOffTimeMs = counter;
}
}
@@ -17152,7 +17001,7 @@
} else {
out.writeInt(0);
}
- for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+ for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (u.mProcessStateTimer[i] != null) {
out.writeInt(1);
u.mProcessStateTimer[i].writeSummaryFromParcelLocked(out, nowRealtime);
@@ -17982,10 +17831,10 @@
}
super.dumpLocked(context, pw, flags, reqUid, histStart);
+ pw.print("Per process state tracking available: ");
+ pw.println(trackPerProcStateCpuTimes());
pw.print("Total cpu time reads: ");
pw.println(mNumSingleUidCpuTimeReads);
- pw.print("Batched cpu time reads: ");
- pw.println(mNumBatchedSingleUidCpuTimeReads);
pw.print("Batching Duration (min): ");
pw.println((mClock.uptimeMillis() - mCpuTimeReadsTrackingStartTimeMs) / (60 * 1000));
pw.print("All UID cpu time reads since the later of device start or stats reset: ");
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index faeb8fc..c801be0 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -620,6 +620,10 @@
}
return numClusterFreqs;
}
+
+ public boolean isFastCpuTimesReader() {
+ return mBpfTimesAvailable;
+ }
}
/**
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index be5dc00..d66f461 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -870,7 +870,8 @@
boolean allowedMinSdk = minDeviceSdk <= Build.VERSION.SDK_INT;
boolean allowedMaxSdk =
maxDeviceSdk == 0 || maxDeviceSdk >= Build.VERSION.SDK_INT;
- if (allowedMinSdk && allowedMaxSdk) {
+ final boolean exists = new File(lfile).exists();
+ if (allowedMinSdk && allowedMaxSdk && exists) {
int bcpSince = XmlUtils.readIntAttribute(parser,
"on-bootclasspath-since", 0);
int bcpBefore = XmlUtils.readIntAttribute(parser,
@@ -880,6 +881,19 @@
? new String[0] : ldependency.split(":"),
bcpSince, bcpBefore);
mSharedLibraries.put(lname, entry);
+ } else {
+ final StringBuilder msg = new StringBuilder(
+ "Ignore shared library ").append(lname).append(":");
+ if (!allowedMinSdk) {
+ msg.append(" min-device-sdk=").append(minDeviceSdk);
+ }
+ if (!allowedMaxSdk) {
+ msg.append(" max-device-sdk=").append(maxDeviceSdk);
+ }
+ if (!exists) {
+ msg.append(" ").append(lfile).append(" does not exist");
+ }
+ Slog.i(TAG, msg.toString());
}
}
} else {
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index a059dd6..78e5adc 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -30,21 +30,19 @@
namespace android {
-static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
- jlong width, jlong height, jint format) {
- String8 str8;
- if (jName) {
- const jchar* str16 = env->GetStringCritical(jName, nullptr);
- if (str16) {
- str8 = String8(reinterpret_cast<const char16_t*>(str16), env->GetStringLength(jName));
- env->ReleaseStringCritical(jName, str16);
- str16 = nullptr;
- }
- }
- std::string name = str8.string();
+static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) {
+ ScopedUtfChars name(env, jName);
+ sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str());
+ queue->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(queue.get());
+}
+
+static jlong nativeCreateAndUpdate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
+ jlong width, jlong height, jint format) {
+ ScopedUtfChars name(env, jName);
sp<BLASTBufferQueue> queue =
- new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
- height, format);
+ new BLASTBufferQueue(name.c_str(), reinterpret_cast<SurfaceControl*>(surfaceControl),
+ width, height, format);
queue->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(queue.get());
}
@@ -96,7 +94,8 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
- {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
+ {"nativeCreate", "(Ljava/lang/String;)J", (void*)nativeCreate},
+ {"nativeCreateAndUpdate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreateAndUpdate},
{"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
{"nativeSetSyncTransaction", "(JJZ)V", (void*)nativeSetSyncTransaction},
diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
index f1ae268..423ef7c 100644
--- a/core/jni/android_media_AudioAttributes.cpp
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -58,7 +58,7 @@
jmethodID setSystemUsage;
jmethodID setInternalCapturePreset;
jmethodID setContentType;
- jmethodID setFlags;
+ jmethodID replaceFlags;
jmethodID addTag;
} gAudioAttributesBuilderMethods;
@@ -130,7 +130,7 @@
gAudioAttributesBuilderMethods.setContentType,
attributes.content_type);
env->CallObjectMethod(jAttributeBuilder.get(),
- gAudioAttributesBuilderMethods.setFlags,
+ gAudioAttributesBuilderMethods.replaceFlags,
attributes.flags);
env->CallObjectMethod(jAttributeBuilder.get(),
gAudioAttributesBuilderMethods.addTag,
@@ -205,8 +205,8 @@
gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie(
env, audioAttributesBuilderClass, "setContentType",
"(I)Landroid/media/AudioAttributes$Builder;");
- gAudioAttributesBuilderMethods.setFlags = GetMethodIDOrDie(
- env, audioAttributesBuilderClass, "setFlags",
+ gAudioAttributesBuilderMethods.replaceFlags = GetMethodIDOrDie(
+ env, audioAttributesBuilderClass, "replaceFlags",
"(I)Landroid/media/AudioAttributes$Builder;");
gAudioAttributesBuilderMethods.addTag = GetMethodIDOrDie(
env, audioAttributesBuilderClass, "addTag",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 04d6171..6e2c807 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5870,6 +5870,10 @@
<!-- Allows input events to be monitored. Very dangerous! @hide -->
<permission android:name="android.permission.MONITOR_INPUT"
android:protectionLevel="signature|recents" />
+ <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current
+ window to the window where the touch currently is on top of. @hide -->
+ <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"
+ android:protectionLevel="signature|recents" />
<!-- Allows the caller to change the associations between input devices and displays.
Very dangerous! @hide -->
<permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 650bd3d..769e667 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1762,6 +1762,8 @@
<string name="face_setup_notification_title">Set up Face Unlock</string>
<!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
<string name="face_setup_notification_content">Unlock your phone by looking at it</string>
+ <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
+ <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
<!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
<string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
<!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ccb487e..134235d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2587,6 +2587,7 @@
<java-symbol type="string" name="face_recalibrate_notification_name" />
<java-symbol type="string" name="face_recalibrate_notification_title" />
<java-symbol type="string" name="face_recalibrate_notification_content" />
+ <java-symbol type="string" name="face_sensor_privacy_enabled" />
<java-symbol type="string" name="face_error_unable_to_process" />
<java-symbol type="string" name="face_error_hw_not_available" />
<java-symbol type="string" name="face_error_no_space" />
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
index 6471492..c3d707c 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
@@ -45,7 +45,6 @@
assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName());
}
- assertEquals(1, leAudioCodecConfig.getMaxCodecType());
assertEquals(codecType, leAudioCodecConfig.getCodecType());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 8ec33bf..c1a45c4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -37,7 +37,6 @@
import android.app.ActivityManager;
import android.os.BatteryStats;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import android.view.Display;
import androidx.test.filters.LargeTest;
@@ -53,6 +52,7 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
public class BatteryStatsImplTest {
private static final long[] CPU_FREQS = {1, 2, 3, 4, 5};
private static final int NUM_CPU_FREQS = CPU_FREQS.length;
@@ -62,29 +62,26 @@
@Mock
private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+ private final MockClock mMockClock = new MockClock();
private MockBatteryStatsImpl mBatteryStatsImpl;
- private MockClock mMockClock = new MockClock();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mKernelUidCpuFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS);
when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
.setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
- .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
- .setTrackingCpuByProcStateEnabled(true);
+ .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
}
@Test
public void testUpdateProcStateCpuTimes() {
mBatteryStatsImpl.setOnBatteryInternal(true);
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
final int[] testUids = {10032, 10048, 10145, 10139};
final int[] activityManagerProcStates = {
@@ -99,15 +96,24 @@
PROCESS_STATE_TOP,
PROCESS_STATE_CACHED
};
- addPendingUids(testUids, testProcStates);
// Initialize time-in-freq counters
mMockClock.realtime = 1000;
for (int i = 0; i < testUids.length; ++i) {
- mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
mockKernelSingleUidTimeReader(testUids[i], new long[5]);
+ mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
}
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
+
+ final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+
+ // Verify there are no cpu times initially.
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
+ assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
+ }
+ }
// Obtain initial CPU time-in-freq counts
final long[][] cpuTimes = {
@@ -117,24 +123,14 @@
{4859048, 348903, 4578967, 5973894, 298549}
};
- final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+ mMockClock.realtime += 1000;
for (int i = 0; i < testUids.length; ++i) {
mockKernelSingleUidTimeReader(testUids[i], cpuTimes[i]);
-
- // Verify there are no cpu times initially.
- final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
- for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
- assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
- assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
- }
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
}
- addPendingUids(testUids, testProcStates);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -155,19 +151,19 @@
{945894, 9089432, 19478, 3834, 7845},
{843895, 43948, 949582, 99, 384}
};
+
+ mMockClock.realtime += 1000;
+
for (int i = 0; i < testUids.length; ++i) {
long[] newCpuTimes = new long[cpuTimes[i].length];
for (int j = 0; j < cpuTimes[i].length; j++) {
newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j];
}
mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
}
- addPendingUids(testUids, testProcStates);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -186,10 +182,8 @@
}
// Validate the on-battery-screen-off counter
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
- mMockClock.realtime * 1000);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
+ mMockClock.realtime * 1000);
final long[][] delta2 = {
{95932, 2943, 49834, 89034, 139},
@@ -197,19 +191,19 @@
{678, 7498, 9843, 889, 4894},
{488, 998, 8498, 394, 574}
};
+
+ mMockClock.realtime += 1000;
+
for (int i = 0; i < testUids.length; ++i) {
long[] newCpuTimes = new long[cpuTimes[i].length];
for (int j = 0; j < cpuTimes[i].length; j++) {
newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j];
}
mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
}
- addPendingUids(testUids, testProcStates);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
-
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -239,24 +233,25 @@
{3049509483598l, 4597834, 377654, 94589035, 7854},
{9493, 784, 99895, 8974893, 9879843}
};
- for (int i = 0; i < testUids.length; ++i) {
- long[] newCpuTimes = new long[cpuTimes[i].length];
- for (int j = 0; j < cpuTimes[i].length; j++) {
- newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
- }
- mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
- }
- addPendingUids(testUids, testProcStates);
+
+ mMockClock.realtime += 1000;
+
final int parentUid = testUids[1];
final int childUid = 99099;
addIsolatedUid(parentUid, childUid);
final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
mockKernelSingleUidTimeReader(childUid, isolatedUidCpuTimes, isolatedUidCpuTimes);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
+ for (int i = 0; i < testUids.length; ++i) {
+ long[] newCpuTimes = new long[cpuTimes[i].length];
+ for (int j = 0; j < cpuTimes[i].length; j++) {
+ newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
+ }
+ mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
+ }
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -284,11 +279,9 @@
}
@Test
- public void testCopyFromAllUidsCpuTimes() {
+ public void testUpdateCpuTimesForAllUids() {
mBatteryStatsImpl.setOnBatteryInternal(false);
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
mMockClock.realtime = 1000;
@@ -299,14 +292,14 @@
PROCESS_STATE_TOP,
PROCESS_STATE_CACHED
};
- addPendingUids(testUids, testProcStates);
for (int i = 0; i < testUids.length; ++i) {
BatteryStatsImpl.Uid uid = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
uid.setProcessStateForTest(testProcStates[i], mMockClock.elapsedRealtime());
mockKernelSingleUidTimeReader(testUids[i], new long[NUM_CPU_FREQS]);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.elapsedRealtime());
}
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
long[][] allCpuTimes = {
@@ -330,9 +323,8 @@
}
mMockClock.realtime += 1000;
- mBatteryStatsImpl.copyFromAllUidsCpuTimes(true, false);
- verifyNoPendingUids();
+ mBatteryStatsImpl.updateCpuTimesForAllUids();
final long[] timeInFreqs = new long[NUM_CPU_FREQS];
@@ -411,9 +403,7 @@
final int releaseTimeMs = 1005;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -436,9 +426,7 @@
final int acquireTimeMs = 1000;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -464,9 +452,7 @@
final int releaseTimeMs_2 = 1009;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -496,9 +482,7 @@
final int releaseTimeMs_2 = 1009;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -523,17 +507,4 @@
final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
u.addIsolatedUid(childUid);
}
-
- private void addPendingUids(int[] uids, int[] procStates) {
- final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
- for (int i = 0; i < uids.length; ++i) {
- pendingUids.put(uids[i], procStates[i]);
- }
- }
-
- private void verifyNoPendingUids() {
- final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
- assertEquals("There shouldn't be any pending uids left: " + pendingUids,
- 0, pendingUids.size());
- }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 441e85d..9172d34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -26,9 +26,6 @@
import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_TRACK_CPU_TIMES_BY_PROC_STATE;
-
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -51,7 +48,6 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DebugUtils;
import android.util.KeyValueListParser;
@@ -125,11 +121,6 @@
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
executeCmd("cmd deviceidle whitelist +" + TEST_PKG);
-
- final ArrayMap<String, String> desiredConstants = new ArrayMap<>();
- desiredConstants.put(KEY_TRACK_CPU_TIMES_BY_PROC_STATE, Boolean.toString(true));
- desiredConstants.put(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS, Integer.toString(0));
- updateBatteryStatsConstants(desiredConstants);
checkCpuTimesAvailability();
}
@@ -517,125 +508,6 @@
batteryOffScreenOn();
}
- @Test
- public void testCpuFreqTimes_trackingDisabled() throws Exception {
- if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
- Log.w(TAG, "Skipping " + testName.getMethodName()
- + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
- + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
- return;
- }
-
- final String bstatsConstants = Settings.Global.getString(sContext.getContentResolver(),
- Settings.Global.BATTERY_STATS_CONSTANTS);
- try {
- batteryOnScreenOn();
- forceStop();
- resetBatteryStats();
- final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null, initial="
- + Arrays.toString(initialSnapshot), initialSnapshot);
- assertNull("Initial top state snapshot should be null",
- getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
-
- doSomeWork(PROCESS_STATE_TOP);
- forceStop();
-
- final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- final String msgCpuTimes = getAllCpuTimesMsg();
- assertCpuTimesValid(cpuTimesMs);
- long actualCpuTimeMs = 0;
- for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
- actualCpuTimeMs += cpuTimesMs[i];
- }
- assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
- WORK_DURATION_MS, actualCpuTimeMs);
-
- updateTrackPerProcStateCpuTimesSetting(bstatsConstants, false);
-
- doSomeWork(PROCESS_STATE_TOP);
- forceStop();
-
- final long[] cpuTimesMs2 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- assertCpuTimesValid(cpuTimesMs2);
- assertCpuTimesEqual(cpuTimesMs2, cpuTimesMs, 20,
- "Unexpected cpu times with tracking off");
-
- updateTrackPerProcStateCpuTimesSetting(bstatsConstants, true);
-
- final long[] cpuTimesMs3 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- assertCpuTimesValid(cpuTimesMs3);
- assertCpuTimesEqual(cpuTimesMs3, cpuTimesMs, 500,
- "Unexpected cpu times after turning on tracking");
-
- doSomeWork(PROCESS_STATE_TOP);
- forceStop();
-
- final long[] cpuTimesMs4 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- assertCpuTimesValid(cpuTimesMs4);
- actualCpuTimeMs = 0;
- for (int i = 0; i < cpuTimesMs4.length / 2; ++i) {
- actualCpuTimeMs += cpuTimesMs4[i];
- }
- assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
- 2 * WORK_DURATION_MS, actualCpuTimeMs);
-
- batteryOffScreenOn();
- } finally {
- Settings.Global.putString(sContext.getContentResolver(),
- Settings.Global.BATTERY_STATS_CONSTANTS, bstatsConstants);
- }
- }
-
- private void assertCpuTimesEqual(long[] actual, long[] expected, long delta, String errMsg) {
- for (int i = actual.length - 1; i >= 0; --i) {
- if (actual[i] > expected[i] + delta || actual[i] < expected[i]) {
- fail(errMsg + ", actual=" + Arrays.toString(actual)
- + ", expected=" + Arrays.toString(expected) + ", delta=" + delta);
- }
- }
- }
-
- private void updateTrackPerProcStateCpuTimesSetting(String originalConstants, boolean enabled)
- throws Exception {
- final String newConstants;
- final String setting = KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=" + enabled;
- if (originalConstants == null || "null".equals(originalConstants)) {
- newConstants = setting;
- } else if (originalConstants.contains(KEY_TRACK_CPU_TIMES_BY_PROC_STATE)) {
- newConstants = originalConstants.replaceAll(
- KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=(true|false)", setting);
- } else {
- newConstants = originalConstants + "," + setting;
- }
- Settings.Global.putString(sContext.getContentResolver(),
- Settings.Global.BATTERY_STATS_CONSTANTS, newConstants);
- assertTrackPerProcStateCpuTimesSetting(enabled);
- }
-
- private void assertTrackPerProcStateCpuTimesSetting(boolean enabled) throws Exception {
- final String expectedValue = Boolean.toString(enabled);
- assertDelayedCondition("Unexpected value for " + KEY_TRACK_CPU_TIMES_BY_PROC_STATE, () -> {
- final String actualValue = getSettingValueFromDump(KEY_TRACK_CPU_TIMES_BY_PROC_STATE);
- return expectedValue.equals(actualValue)
- ? null : "expected=" + expectedValue + ", actual=" + actualValue;
- }, SETTING_UPDATE_TIMEOUT_MS, SETTING_UPDATE_CHECK_INTERVAL_MS);
- }
-
- private String getSettingValueFromDump(String key) throws Exception {
- final String settingsDump = executeCmdSilent("dumpsys batterystats --settings");
- final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
- splitter.setString(settingsDump);
- String next;
- while (splitter.hasNext()) {
- next = splitter.next().trim();
- if (next.startsWith(key)) {
- return next.split("=")[1];
- }
- }
- return null;
- }
-
private void assertCpuTimesValid(long[] cpuTimes) {
assertNotNull(cpuTimes);
for (int i = 0; i < cpuTimes.length; ++i) {
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 1ae30db..d5b0f0a 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -98,6 +98,8 @@
new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+ when(mMockCpuUidFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
+
mStatsRule.getBatteryStats()
.setUserInfoProvider(mMockUserInfoProvider)
.setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
@@ -277,8 +279,6 @@
@Test
public void testTimerBasedModel_byProcessState() {
- mStatsRule.getBatteryStats().setTrackingCpuByProcStateEnabled(true);
-
when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
when(mMockCpuUidFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
@@ -311,7 +311,7 @@
}).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
- mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+ mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
mockSingleUidTimeReader(APP_UID1, new long[]{1000, 2000, 3000, 4000});
mockSingleUidTimeReader(APP_UID2, new long[]{1111, 2222, 3333, 4444});
@@ -326,7 +326,7 @@
}).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
- mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+ mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
mockSingleUidTimeReader(APP_UID1, new long[] {5000, 6000, 7000, 8000});
mockSingleUidTimeReader(APP_UID2, new long[]{5555, 6666, 7777, 8888});
@@ -346,7 +346,7 @@
}).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
- mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+ mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
CpuPowerCalculator calculator =
new CpuPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index d16689c..e4c83f1 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,10 +16,13 @@
package com.android.internal.os;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.net.NetworkStats;
import android.os.Handler;
import android.os.Looper;
-import android.util.SparseIntArray;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -59,6 +62,9 @@
// A no-op handler.
mHandler = new Handler(Looper.getMainLooper()) {
};
+
+ mCpuUidFreqTimeReader = mock(KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader.class);
+ when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200});
}
public void initMeasuredEnergyStats(String[] customBucketNames) {
@@ -178,15 +184,6 @@
return this;
}
- public MockBatteryStatsImpl setTrackingCpuByProcStateEnabled(boolean enabled) {
- mConstants.TRACK_CPU_TIMES_BY_PROC_STATE = enabled;
- return this;
- }
-
- public SparseIntArray getPendingUids() {
- return mPendingUids;
- }
-
public int getAndClearExternalStatsSyncFlags() {
final int flags = mExternalStatsSync.flags;
mExternalStatsSync.flags = 0;
@@ -217,18 +214,6 @@
}
@Override
- public Future<?> scheduleReadProcStateCpuTimes(boolean onBattery,
- boolean onBatteryScreenOff, long delayMillis) {
- return null;
- }
-
- @Override
- public Future<?> scheduleCopyFromAllUidsCpuTimes(
- boolean onBattery, boolean onBatteryScreenOff) {
- return null;
- }
-
- @Override
public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
flags |= flag;
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 70a3fc7..60cb9d3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -517,6 +517,8 @@
<permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
<!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
<permission name="android.permission.LOCK_DEVICE" />
+ <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 8844370..8d3eadb 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -27,8 +27,9 @@
// Note: This field is accessed by native code.
public long mNativeObject; // BLASTBufferQueue*
- private static native long nativeCreate(String name, long surfaceControl, long width,
+ private static native long nativeCreateAndUpdate(String name, long surfaceControl, long width,
long height, int format);
+ private static native long nativeCreate(String name);
private static native void nativeDestroy(long ptr);
private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
private static native void nativeSetSyncTransaction(long ptr, long transactionPtr,
@@ -43,7 +44,11 @@
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@PixelFormat.Format int format) {
- mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
+ mNativeObject = nativeCreateAndUpdate(name, sc.mNativeObject, width, height, format);
+ }
+
+ public BLASTBufferQueue(String name) {
+ mNativeObject = nativeCreate(name);
}
public void destroy() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index dc4e27a..af19bd0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -860,4 +860,12 @@
launchingContainer.getTaskFragmentToken());
}
}
+
+ /**
+ * Checks if an activity is embedded and its presentation is customized by a
+ * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
+ */
+ public boolean isActivityEmbedded(@NonNull Activity activity) {
+ return mPresenter.isActivityEmbedded(activity.getActivityToken());
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index a1a53bc..4d2d055 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -103,7 +103,7 @@
ActivityThread activityThread = ActivityThread.currentActivityThread();
for (IBinder token : mInfo.getActivities()) {
Activity activity = activityThread.getActivity(token);
- if (activity != null && !allActivities.contains(activity)) {
+ if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) {
allActivities.add(activity);
}
}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index d6678bf..f54ab08 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 82e8273..da4bbe8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -189,7 +189,7 @@
mEnterSplitButton = findViewById(R.id.enter_split);
mEnterSplitButton.setAlpha(0);
mEnterSplitButton.setOnClickListener(v -> {
- if (mMenuContainer.getAlpha() != 0) {
+ if (mEnterSplitButton.getAlpha() != 0) {
enterSplit();
}
});
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 46aad3f..d721291 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -89,6 +89,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
/**
* AudioManager provides access to volume and ringer mode control.
@@ -5775,6 +5776,23 @@
}
/**
+ * Indicate wired accessory connection state change.
+ * @param device {@link AudioDeviceAttributes} of the device to "fake-connect"
+ * @param connected true for connected, false for disconnected
+ * {@hide}
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ boolean connected) {
+ try {
+ getService().setTestDeviceConnectionState(device, connected);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Indicate Bluetooth profile connection state change.
* Configuration changes for A2DP are indicated by having the same <code>newDevice</code> and
* <code>previousDevice</code>
@@ -7968,6 +7986,231 @@
}
//---------------------------------------------------------
+ // audio device connection-dependent muting
+ /**
+ * @hide
+ * Mute a set of playback use cases until a given audio device is connected.
+ * Automatically unmute upon connection of the device, or after the given timeout, whichever
+ * happens first.
+ * @param usagesToMute non-empty array of {@link AudioAttributes} usages (for example
+ * {@link AudioAttributes#USAGE_MEDIA}) to mute until the
+ * device connects
+ * @param device the audio device expected to connect within the timeout duration
+ * @param timeout the maximum amount of time to wait for the device connection
+ * @param timeUnit the unit for the timeout
+ * @throws IllegalStateException when trying to issue the command while another is already in
+ * progress and hasn't been cancelled by
+ * {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}. See
+ * {@link #getMutingExpectedDevice()} to check if a muting command is active.
+ * @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void muteAwaitConnection(@NonNull int[] usagesToMute,
+ @NonNull AudioDeviceAttributes device,
+ long timeout, @NonNull TimeUnit timeUnit) throws IllegalStateException {
+ if (timeout <= 0) {
+ throw new IllegalArgumentException("Timeout must be greater than 0");
+ }
+ Objects.requireNonNull(usagesToMute);
+ if (usagesToMute.length == 0) {
+ throw new IllegalArgumentException("Array of usages to mute cannot be empty");
+ }
+ Objects.requireNonNull(device);
+ Objects.requireNonNull(timeUnit);
+ try {
+ getService().muteAwaitConnection(usagesToMute, device, timeUnit.toMillis(timeout));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Query which audio device, if any, is causing some playback use cases to be muted until it
+ * connects.
+ * @return the audio device used in
+ * {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}, or null
+ * if there is no active muting command (either because the muting command was not issued
+ * or because it timed out)
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+ try {
+ return getService().getMutingExpectedDevice();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Cancel a {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
+ * command.
+ * @param device the device whose connection was expected when the {@code muteAwaitConnection}
+ * command was issued.
+ * @throws IllegalStateException when trying to issue the command for a device whose connection
+ * is not anticipated by a previous call to
+ * {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device)
+ throws IllegalStateException {
+ Objects.requireNonNull(device);
+ try {
+ getService().cancelMuteAwaitConnection(device);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * A callback class to receive events about the muting and unmuting of playback use cases
+ * conditional on the upcoming connection of an audio device.
+ * @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
+ */
+ @SystemApi
+ public abstract static class MuteAwaitConnectionCallback {
+
+ /**
+ * An event where the expected audio device connected
+ * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+ */
+ public static final int EVENT_CONNECTION = 1;
+ /**
+ * An event where the expected audio device failed connect before the timeout happened
+ * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+ */
+ public static final int EVENT_TIMEOUT = 2;
+ /**
+ * An event where the {@code muteAwaitConnection()} command
+ * was cancelled with {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}
+ * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+ */
+ public static final int EVENT_CANCEL = 3;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = "EVENT_", value = {
+ EVENT_CONNECTION,
+ EVENT_TIMEOUT,
+ EVENT_CANCEL }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnmuteEvent {}
+
+ /**
+ * Called when a number of playback use cases are muted in response to a call to
+ * {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}.
+ * @param device the audio device whose connection is expected. Playback use cases are
+ * unmuted when that device connects
+ * @param mutedUsages an array of {@link AudioAttributes} usages that describe the affected
+ * playback use cases.
+ */
+ public void onMutedUntilConnection(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull int[] mutedUsages) {}
+
+ /**
+ * Called when an event occurred that caused playback uses cases to be unmuted
+ * @param unmuteEvent the nature of the event
+ * @param device the device that was expected to connect
+ * @param mutedUsages the array of {@link AudioAttributes} usages that were muted until
+ * the event occurred
+ */
+ public void onUnmutedEvent(
+ @UnmuteEvent int unmuteEvent,
+ @NonNull AudioDeviceAttributes device, @NonNull int[] mutedUsages) {}
+ }
+
+
+ /**
+ * @hide
+ * Register a callback to receive updates on the playback muting conditional on a specific
+ * audio device connection.
+ * @param executor the {@link Executor} handling the callback
+ * @param callback the callback to register
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void registerMuteAwaitConnectionCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull MuteAwaitConnectionCallback callback) {
+ synchronized (mMuteAwaitConnectionListenerLock) {
+ final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
+ MuteAwaitConnectionDispatcherStub> res =
+ CallbackUtil.addListener("registerMuteAwaitConnectionCallback",
+ executor, callback, mMuteAwaitConnectionListeners,
+ mMuteAwaitConnDispatcherStub,
+ () -> new MuteAwaitConnectionDispatcherStub(),
+ stub -> stub.register(true));
+ mMuteAwaitConnectionListeners = res.first;
+ mMuteAwaitConnDispatcherStub = res.second;
+ }
+ }
+
+ /**
+ * @hide
+ * Unregister a previously registered callback for playback muting conditional on device
+ * connection.
+ * @param callback the callback to unregister
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void unregisterMuteAwaitConnectionCallback(
+ @NonNull MuteAwaitConnectionCallback callback) {
+ synchronized (mMuteAwaitConnectionListenerLock) {
+ final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
+ MuteAwaitConnectionDispatcherStub> res =
+ CallbackUtil.removeListener("unregisterMuteAwaitConnectionCallback",
+ callback, mMuteAwaitConnectionListeners, mMuteAwaitConnDispatcherStub,
+ stub -> stub.register(false));
+ mMuteAwaitConnectionListeners = res.first;
+ mMuteAwaitConnDispatcherStub = res.second;
+ }
+ }
+
+ private final Object mMuteAwaitConnectionListenerLock = new Object();
+
+ @GuardedBy("mMuteAwaitConnectionListenerLock")
+ private @Nullable ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>
+ mMuteAwaitConnectionListeners;
+
+ @GuardedBy("mMuteAwaitConnectionListenerLock")
+ private MuteAwaitConnectionDispatcherStub mMuteAwaitConnDispatcherStub;
+
+ private final class MuteAwaitConnectionDispatcherStub
+ extends IMuteAwaitConnectionCallback.Stub {
+ public void register(boolean register) {
+ try {
+ getService().registerMuteAwaitConnectionDispatcher(this, register);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ @SuppressLint("GuardedBy") // lock applied inside callListeners method
+ public void dispatchOnMutedUntilConnection(AudioDeviceAttributes device,
+ int[] mutedUsages) {
+ CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
+ mMuteAwaitConnectionListenerLock,
+ (listener) -> listener.onMutedUntilConnection(device, mutedUsages));
+ }
+
+ @Override
+ @SuppressLint("GuardedBy") // lock applied inside callListeners method
+ public void dispatchOnUnmutedEvent(int event, AudioDeviceAttributes device,
+ int[] mutedUsages) {
+ CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
+ mMuteAwaitConnectionListenerLock,
+ (listener) -> listener.onUnmutedEvent(event, device, mutedUsages));
+ }
+ }
+
+ //---------------------------------------------------------
// Inner classes
//--------------------
/**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index f15f880..afcbc57 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -32,6 +32,7 @@
import android.media.IAudioServerStateDispatcher;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
+import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -447,4 +448,16 @@
boolean isVolumeFixed();
boolean isPstnCallAudioInterceptable();
+
+ oneway void muteAwaitConnection(in int[] usagesToMute, in AudioDeviceAttributes dev,
+ long timeOutMs);
+
+ oneway void cancelMuteAwaitConnection(in AudioDeviceAttributes dev);
+
+ AudioDeviceAttributes getMutingExpectedDevice();
+
+ void registerMuteAwaitConnectionDispatcher(in IMuteAwaitConnectionCallback cb,
+ boolean register);
+
+ void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected);
}
diff --git a/media/java/android/media/IMuteAwaitConnectionCallback.aidl b/media/java/android/media/IMuteAwaitConnectionCallback.aidl
new file mode 100644
index 0000000..77fc029
--- /dev/null
+++ b/media/java/android/media/IMuteAwaitConnectionCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.AudioDeviceAttributes;
+
+/**
+ * AIDL for the AudioService to signal mute events tied to audio device connections.
+ *
+ * {@hide}
+ */
+oneway interface IMuteAwaitConnectionCallback {
+
+ void dispatchOnMutedUntilConnection(in AudioDeviceAttributes device, in int[] mutedUsages);
+
+ void dispatchOnUnmutedEvent(int event, in AudioDeviceAttributes device, in int[] mutedUsages);
+}
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 4d8eda1..c439356 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -23,19 +23,35 @@
/** @hide */
public abstract class BroadcastInfoRequest implements Parcelable {
- protected static final int PARCEL_TOKEN_TS_REQUEST = 1;
+
+ // todo: change const declaration to intdef
+ public static final int REQUEST_OPTION_REPEAT = 11;
+ public static final int REQUEST_OPTION_AUTO_UPDATE = 12;
public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
new Parcelable.Creator<BroadcastInfoRequest>() {
@Override
public BroadcastInfoRequest createFromParcel(Parcel source) {
- int token = source.readInt();
- switch (token) {
- case PARCEL_TOKEN_TS_REQUEST:
+ int type = source.readInt();
+ switch (type) {
+ case BroadcastInfoType.TS:
return TsRequest.createFromParcelBody(source);
+ case BroadcastInfoType.TABLE:
+ return TableRequest.createFromParcelBody(source);
+ case BroadcastInfoType.SECTION:
+ return SectionRequest.createFromParcelBody(source);
+ case BroadcastInfoType.PES:
+ return PesRequest.createFromParcelBody(source);
+ case BroadcastInfoType.STREAM_EVENT:
+ return StreamEventRequest.createFromParcelBody(source);
+ case BroadcastInfoType.DSMCC:
+ return DsmccRequest.createFromParcelBody(source);
+ case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+ return TvProprietaryFunctionRequest.createFromParcelBody(source);
default:
throw new IllegalStateException(
- "Unexpected broadcast info request type token in parcel.");
+ "Unexpected broadcast info request type (value "
+ + type + ") in parcel.");
}
}
@@ -45,18 +61,32 @@
}
};
- int requestId;
+ protected final int mType;
+ protected final int mRequestId;
+ protected final int mOption;
- public BroadcastInfoRequest(int requestId) {
- this.requestId = requestId;
+ protected BroadcastInfoRequest(int type, int requestId, int option) {
+ mType = type;
+ mRequestId = requestId;
+ mOption = option;
}
- protected BroadcastInfoRequest(Parcel source) {
- requestId = source.readInt();
+ protected BroadcastInfoRequest(int type, Parcel source) {
+ mType = type;
+ mRequestId = source.readInt();
+ mOption = source.readInt();
+ }
+
+ public int getType() {
+ return mType;
}
public int getRequestId() {
- return requestId;
+ return mRequestId;
+ }
+
+ public int getOption() {
+ return mOption;
}
@Override
@@ -66,6 +96,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(requestId);
+ dest.writeInt(mType);
+ dest.writeInt(mRequestId);
+ dest.writeInt(mOption);
}
}
diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java
index fe4e8b7..288f2f9 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -22,12 +22,37 @@
import android.annotation.NonNull;
/** @hide */
-public final class BroadcastInfoResponse implements Parcelable {
+public abstract class BroadcastInfoResponse implements Parcelable {
+ // todo: change const declaration to intdef
+ public static final int ERROR = 1;
+ public static final int OK = 2;
+ public static final int CANCEL = 3;
+
public static final @NonNull Parcelable.Creator<BroadcastInfoResponse> CREATOR =
new Parcelable.Creator<BroadcastInfoResponse>() {
@Override
public BroadcastInfoResponse createFromParcel(Parcel source) {
- return new BroadcastInfoResponse(source);
+ int type = source.readInt();
+ switch (type) {
+ case BroadcastInfoType.TS:
+ return TsResponse.createFromParcelBody(source);
+ case BroadcastInfoType.TABLE:
+ return TableResponse.createFromParcelBody(source);
+ case BroadcastInfoType.SECTION:
+ return SectionResponse.createFromParcelBody(source);
+ case BroadcastInfoType.PES:
+ return PesResponse.createFromParcelBody(source);
+ case BroadcastInfoType.STREAM_EVENT:
+ return StreamEventResponse.createFromParcelBody(source);
+ case BroadcastInfoType.DSMCC:
+ return DsmccResponse.createFromParcelBody(source);
+ case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+ return TvProprietaryFunctionResponse.createFromParcelBody(source);
+ default:
+ throw new IllegalStateException(
+ "Unexpected broadcast info response type (value "
+ + type + ") in parcel.");
+ }
}
@Override
@@ -36,18 +61,39 @@
}
};
- int requestId;
+ protected final int mType;
+ protected final int mRequestId;
+ protected final int mSequence;
+ protected final int mResponseResult;
- public BroadcastInfoResponse(int requestId) {
- this.requestId = requestId;
+ protected BroadcastInfoResponse(int type, int requestId, int sequence, int responseResult) {
+ mType = type;
+ mRequestId = requestId;
+ mSequence = sequence;
+ mResponseResult = responseResult;
}
- private BroadcastInfoResponse(Parcel source) {
- requestId = source.readInt();
+ protected BroadcastInfoResponse(int type, Parcel source) {
+ mType = type;
+ mRequestId = source.readInt();
+ mSequence = source.readInt();
+ mResponseResult = source.readInt();
+ }
+
+ public int getType() {
+ return mType;
}
public int getRequestId() {
- return requestId;
+ return mRequestId;
+ }
+
+ public int getSequence() {
+ return mSequence;
+ }
+
+ public int getResponseResult() {
+ return mResponseResult;
}
@Override
@@ -57,6 +103,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(requestId);
+ dest.writeInt(mType);
+ dest.writeInt(mRequestId);
+ dest.writeInt(mSequence);
+ dest.writeInt(mResponseResult);
}
}
diff --git a/media/java/android/media/tv/BroadcastInfoType.java b/media/java/android/media/tv/BroadcastInfoType.java
new file mode 100644
index 0000000..e7a0595
--- /dev/null
+++ b/media/java/android/media/tv/BroadcastInfoType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+/** @hide */
+public final class BroadcastInfoType {
+ // todo: change const declaration to intdef in TvInputManager
+ public static final int TS = 1;
+ public static final int TABLE = 2;
+ public static final int SECTION = 3;
+ public static final int PES = 4;
+ public static final int STREAM_EVENT = 5;
+ public static final int DSMCC = 6;
+ public static final int TV_PROPRIETARY_FUNCTION = 7;
+
+ private BroadcastInfoType() {
+ }
+}
diff --git a/media/java/android/media/tv/DsmccRequest.java b/media/java/android/media/tv/DsmccRequest.java
new file mode 100644
index 0000000..f2e4750
--- /dev/null
+++ b/media/java/android/media/tv/DsmccRequest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.DSMCC;
+
+ public static final @NonNull Parcelable.Creator<DsmccRequest> CREATOR =
+ new Parcelable.Creator<DsmccRequest>() {
+ @Override
+ public DsmccRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public DsmccRequest[] newArray(int size) {
+ return new DsmccRequest[size];
+ }
+ };
+
+ private final Uri mUri;
+
+ public static DsmccRequest createFromParcelBody(Parcel in) {
+ return new DsmccRequest(in);
+ }
+
+ public DsmccRequest(int requestId, int option, Uri uri) {
+ super(requestType, requestId, option);
+ mUri = uri;
+ }
+
+ protected DsmccRequest(Parcel source) {
+ super(requestType, source);
+ String uriString = source.readString();
+ mUri = uriString == null ? null : Uri.parse(uriString);
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ String uriString = mUri == null ? null : mUri.toString();
+ dest.writeString(uriString);
+ }
+}
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
new file mode 100644
index 0000000..3bdfb95
--- /dev/null
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.DSMCC;
+
+ public static final @NonNull Parcelable.Creator<DsmccResponse> CREATOR =
+ new Parcelable.Creator<DsmccResponse>() {
+ @Override
+ public DsmccResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public DsmccResponse[] newArray(int size) {
+ return new DsmccResponse[size];
+ }
+ };
+
+ private final ParcelFileDescriptor mFile;
+
+ public static DsmccResponse createFromParcelBody(Parcel in) {
+ return new DsmccResponse(in);
+ }
+
+ public DsmccResponse(int requestId, int sequence, int responseResult,
+ ParcelFileDescriptor file) {
+ super(responseType, requestId, sequence, responseResult);
+ mFile = file;
+ }
+
+ protected DsmccResponse(Parcel source) {
+ super(responseType, source);
+ mFile = source.readFileDescriptor();
+ }
+
+ public ParcelFileDescriptor getFile() {
+ return mFile;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ mFile.writeToParcel(dest, flags);
+ }
+}
diff --git a/media/java/android/media/tv/PesRequest.java b/media/java/android/media/tv/PesRequest.java
new file mode 100644
index 0000000..0e444b8
--- /dev/null
+++ b/media/java/android/media/tv/PesRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.PES;
+
+ public static final @NonNull Parcelable.Creator<PesRequest> CREATOR =
+ new Parcelable.Creator<PesRequest>() {
+ @Override
+ public PesRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public PesRequest[] newArray(int size) {
+ return new PesRequest[size];
+ }
+ };
+
+ private final int mTsPid;
+ private final int mStreamId;
+
+ public static PesRequest createFromParcelBody(Parcel in) {
+ return new PesRequest(in);
+ }
+
+ public PesRequest(int requestId, int option, int tsPid, int streamId) {
+ super(requestType, requestId, option);
+ mTsPid = tsPid;
+ mStreamId = streamId;
+ }
+
+ protected PesRequest(Parcel source) {
+ super(requestType, source);
+ mTsPid = source.readInt();
+ mStreamId = source.readInt();
+ }
+
+ public int getTsPid() {
+ return mTsPid;
+ }
+
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mTsPid);
+ dest.writeInt(mStreamId);
+ }
+}
diff --git a/media/java/android/media/tv/PesResponse.java b/media/java/android/media/tv/PesResponse.java
new file mode 100644
index 0000000..d46e6fc
--- /dev/null
+++ b/media/java/android/media/tv/PesResponse.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.PES;
+
+ public static final @NonNull Parcelable.Creator<PesResponse> CREATOR =
+ new Parcelable.Creator<PesResponse>() {
+ @Override
+ public PesResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public PesResponse[] newArray(int size) {
+ return new PesResponse[size];
+ }
+ };
+
+ private final String mSharedFilterToken;
+
+ public static PesResponse createFromParcelBody(Parcel in) {
+ return new PesResponse(in);
+ }
+
+ public PesResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+ super(responseType, requestId, sequence, responseResult);
+ mSharedFilterToken = sharedFilterToken;
+ }
+
+ protected PesResponse(Parcel source) {
+ super(responseType, source);
+ mSharedFilterToken = source.readString();
+ }
+
+ public String getSharedFilterToken() {
+ return mSharedFilterToken;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mSharedFilterToken);
+ }
+}
diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java
new file mode 100644
index 0000000..3e8e909
--- /dev/null
+++ b/media/java/android/media/tv/SectionRequest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.SECTION;
+
+ public static final @NonNull Parcelable.Creator<SectionRequest> CREATOR =
+ new Parcelable.Creator<SectionRequest>() {
+ @Override
+ public SectionRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public SectionRequest[] newArray(int size) {
+ return new SectionRequest[size];
+ }
+ };
+
+ private final int mTsPid;
+ private final int mTableId;
+ private final int mVersion;
+
+ public static SectionRequest createFromParcelBody(Parcel in) {
+ return new SectionRequest(in);
+ }
+
+ public SectionRequest(int requestId, int option, int tsPid, int tableId, int version) {
+ super(requestType, requestId, option);
+ mTsPid = tsPid;
+ mTableId = tableId;
+ mVersion = version;
+ }
+
+ protected SectionRequest(Parcel source) {
+ super(requestType, source);
+ mTsPid = source.readInt();
+ mTableId = source.readInt();
+ mVersion = source.readInt();
+ }
+
+ public int getTsPid() {
+ return mTsPid;
+ }
+
+ public int getTableId() {
+ return mTableId;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mTsPid);
+ dest.writeInt(mTableId);
+ dest.writeInt(mVersion);
+ }
+}
diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java
new file mode 100644
index 0000000..1c8f965
--- /dev/null
+++ b/media/java/android/media/tv/SectionResponse.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.SECTION;
+
+ public static final @NonNull Parcelable.Creator<SectionResponse> CREATOR =
+ new Parcelable.Creator<SectionResponse>() {
+ @Override
+ public SectionResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public SectionResponse[] newArray(int size) {
+ return new SectionResponse[size];
+ }
+ };
+
+ private final int mSessionId;
+ private final int mVersion;
+ private final Bundle mSessionData;
+
+ public static SectionResponse createFromParcelBody(Parcel in) {
+ return new SectionResponse(in);
+ }
+
+ public SectionResponse(int requestId, int sequence, int responseResult, int sessionId,
+ int version, Bundle sessionData) {
+ super(responseType, requestId, sequence, responseResult);
+ mSessionId = sessionId;
+ mVersion = version;
+ mSessionData = sessionData;
+ }
+
+ protected SectionResponse(Parcel source) {
+ super(responseType, source);
+ mSessionId = source.readInt();
+ mVersion = source.readInt();
+ mSessionData = source.readBundle();
+ }
+
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public Bundle getSessionData() {
+ return mSessionData;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mSessionId);
+ dest.writeInt(mVersion);
+ dest.writeBundle(mSessionData);
+ }
+}
diff --git a/media/java/android/media/tv/StreamEventRequest.java b/media/java/android/media/tv/StreamEventRequest.java
new file mode 100644
index 0000000..09399c2
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventRequest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.STREAM_EVENT;
+
+ public static final @NonNull Parcelable.Creator<StreamEventRequest> CREATOR =
+ new Parcelable.Creator<StreamEventRequest>() {
+ @Override
+ public StreamEventRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public StreamEventRequest[] newArray(int size) {
+ return new StreamEventRequest[size];
+ }
+ };
+
+ private final Uri mTargetUri;
+ private final String mEventName;
+
+ public static StreamEventRequest createFromParcelBody(Parcel in) {
+ return new StreamEventRequest(in);
+ }
+
+ public StreamEventRequest(int requestId, int option, Uri targetUri, String eventName) {
+ super(requestType, requestId, option);
+ this.mTargetUri = targetUri;
+ this.mEventName = eventName;
+ }
+
+ protected StreamEventRequest(Parcel source) {
+ super(requestType, source);
+ String uriString = source.readString();
+ mTargetUri = uriString == null ? null : Uri.parse(uriString);
+ mEventName = source.readString();
+ }
+
+ public Uri getTargetUri() {
+ return mTargetUri;
+ }
+
+ public String getEventName() {
+ return mEventName;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ String uriString = mTargetUri == null ? null : mTargetUri.toString();
+ dest.writeString(uriString);
+ dest.writeString(mEventName);
+ }
+}
diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java
new file mode 100644
index 0000000..027b735
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventResponse.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.STREAM_EVENT;
+
+ public static final @NonNull Parcelable.Creator<StreamEventResponse> CREATOR =
+ new Parcelable.Creator<StreamEventResponse>() {
+ @Override
+ public StreamEventResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public StreamEventResponse[] newArray(int size) {
+ return new StreamEventResponse[size];
+ }
+ };
+
+ private final String mName;
+ private final String mText;
+ private final String mData;
+ private final String mStatus;
+
+ public static StreamEventResponse createFromParcelBody(Parcel in) {
+ return new StreamEventResponse(in);
+ }
+
+ public StreamEventResponse(int requestId, int sequence, int responseResult, String name,
+ String text, String data, String status) {
+ super(responseType, requestId, sequence, responseResult);
+ mName = name;
+ mText = text;
+ mData = data;
+ mStatus = status;
+ }
+
+ protected StreamEventResponse(Parcel source) {
+ super(responseType, source);
+ mName = source.readString();
+ mText = source.readString();
+ mData = source.readString();
+ mStatus = source.readString();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ public String getData() {
+ return mData;
+ }
+
+ public String getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mName);
+ dest.writeString(mText);
+ dest.writeString(mData);
+ dest.writeString(mStatus);
+ }
+}
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
new file mode 100644
index 0000000..5432215
--- /dev/null
+++ b/media/java/android/media/tv/TableRequest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TableRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.TABLE;
+
+ // todo: change const declaration to intdef
+ public static final int PAT = 1;
+ public static final int PMT = 2;
+
+ public static final @NonNull Parcelable.Creator<TableRequest> CREATOR =
+ new Parcelable.Creator<TableRequest>() {
+ @Override
+ public TableRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TableRequest[] newArray(int size) {
+ return new TableRequest[size];
+ }
+ };
+
+ private final int mTableId;
+ private final int mTableName;
+ private final int mVersion;
+
+ public static TableRequest createFromParcelBody(Parcel in) {
+ return new TableRequest(in);
+ }
+
+ public TableRequest(int requestId, int option, int tableId, int tableName, int version) {
+ super(requestType, requestId, option);
+ mTableId = tableId;
+ mTableName = tableName;
+ mVersion = version;
+ }
+
+ protected TableRequest(Parcel source) {
+ super(requestType, source);
+ mTableId = source.readInt();
+ mTableName = source.readInt();
+ mVersion = source.readInt();
+ }
+
+ public int getTableId() {
+ return mTableId;
+ }
+
+ public int getTableName() {
+ return mTableName;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mTableId);
+ dest.writeInt(mTableName);
+ dest.writeInt(mVersion);
+ }
+}
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
new file mode 100644
index 0000000..a6d3e39
--- /dev/null
+++ b/media/java/android/media/tv/TableResponse.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.net.Uri;
+
+/** @hide */
+public class TableResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.TABLE;
+
+ public static final @NonNull Parcelable.Creator<TableResponse> CREATOR =
+ new Parcelable.Creator<TableResponse>() {
+ @Override
+ public TableResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TableResponse[] newArray(int size) {
+ return new TableResponse[size];
+ }
+ };
+
+ private final Uri mTableUri;
+ private final int mVersion;
+ private final int mSize;
+
+ public static TableResponse createFromParcelBody(Parcel in) {
+ return new TableResponse(in);
+ }
+
+ public TableResponse(int requestId, int sequence, int responseResult, Uri tableUri,
+ int version, int size) {
+ super(responseType, requestId, sequence, responseResult);
+ mTableUri = tableUri;
+ mVersion = version;
+ mSize = size;
+ }
+
+ protected TableResponse(Parcel source) {
+ super(responseType, source);
+ String uriString = source.readString();
+ mTableUri = uriString == null ? null : Uri.parse(uriString);
+ mVersion = source.readInt();
+ mSize = source.readInt();
+ }
+
+ public Uri getTableUri() {
+ return mTableUri;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ String uriString = mTableUri == null ? null : mTableUri.toString();
+ dest.writeString(uriString);
+ dest.writeInt(mVersion);
+ dest.writeInt(mSize);
+ }
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/media/java/android/media/tv/TsRequest.aidl
deleted file mode 100644
index 0abc7ec..0000000
--- a/media/java/android/media/tv/TsRequest.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv;
-
-parcelable TsRequest;
diff --git a/media/java/android/media/tv/TsRequest.java b/media/java/android/media/tv/TsRequest.java
index 3690d05..141f3ac 100644
--- a/media/java/android/media/tv/TsRequest.java
+++ b/media/java/android/media/tv/TsRequest.java
@@ -22,6 +22,8 @@
/** @hide */
public class TsRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.TS;
+
public static final @NonNull Parcelable.Creator<TsRequest> CREATOR =
new Parcelable.Creator<TsRequest>() {
@Override
@@ -36,30 +38,29 @@
}
};
- int tsPid;
+ private final int mTsPid;
public static TsRequest createFromParcelBody(Parcel in) {
return new TsRequest(in);
}
- public TsRequest(int requestId, int tsPid) {
- super(requestId);
- this.tsPid = tsPid;
+ public TsRequest(int requestId, int option, int tsPid) {
+ super(requestType, requestId, option);
+ mTsPid = tsPid;
}
protected TsRequest(Parcel source) {
- super(source);
- tsPid = source.readInt();
+ super(requestType, source);
+ mTsPid = source.readInt();
}
public int getTsPid() {
- return tsPid;
+ return mTsPid;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(PARCEL_TOKEN_TS_REQUEST);
super.writeToParcel(dest, flags);
- dest.writeInt(tsPid);
+ dest.writeInt(mTsPid);
}
}
diff --git a/media/java/android/media/tv/TsResponse.java b/media/java/android/media/tv/TsResponse.java
new file mode 100644
index 0000000..e30ff54
--- /dev/null
+++ b/media/java/android/media/tv/TsResponse.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TsResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.TS;
+
+ public static final @NonNull Parcelable.Creator<TsResponse> CREATOR =
+ new Parcelable.Creator<TsResponse>() {
+ @Override
+ public TsResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TsResponse[] newArray(int size) {
+ return new TsResponse[size];
+ }
+ };
+
+ private final String mSharedFilterToken;
+
+ public static TsResponse createFromParcelBody(Parcel in) {
+ return new TsResponse(in);
+ }
+
+ public TsResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+ super(responseType, requestId, sequence, responseResult);
+ this.mSharedFilterToken = sharedFilterToken;
+ }
+
+ protected TsResponse(Parcel source) {
+ super(responseType, source);
+ mSharedFilterToken = source.readString();
+ }
+
+ public String getSharedFilterToken() {
+ return mSharedFilterToken;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mSharedFilterToken);
+ }
+}
diff --git a/media/java/android/media/tv/TvProprietaryFunctionRequest.java b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
new file mode 100644
index 0000000..845641d
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+ public static final @NonNull Parcelable.Creator<TvProprietaryFunctionRequest> CREATOR =
+ new Parcelable.Creator<TvProprietaryFunctionRequest>() {
+ @Override
+ public TvProprietaryFunctionRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TvProprietaryFunctionRequest[] newArray(int size) {
+ return new TvProprietaryFunctionRequest[size];
+ }
+ };
+
+ private final String mNameSpace;
+ private final String mName;
+ private final String mArguments;
+
+ public static TvProprietaryFunctionRequest createFromParcelBody(Parcel in) {
+ return new TvProprietaryFunctionRequest(in);
+ }
+
+ public TvProprietaryFunctionRequest(int requestId, int option, String nameSpace,
+ String name, String arguments) {
+ super(requestType, requestId, option);
+ mNameSpace = nameSpace;
+ mName = name;
+ mArguments = arguments;
+ }
+
+ protected TvProprietaryFunctionRequest(Parcel source) {
+ super(requestType, source);
+ mNameSpace = source.readString();
+ mName = source.readString();
+ mArguments = source.readString();
+ }
+
+ public String getNameSpace() {
+ return mNameSpace;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getArguments() {
+ return mArguments;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mNameSpace);
+ dest.writeString(mName);
+ dest.writeString(mArguments);
+ }
+}
diff --git a/media/java/android/media/tv/TvProprietaryFunctionResponse.java b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
new file mode 100644
index 0000000..3181b08
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+ public static final @NonNull Parcelable.Creator<TvProprietaryFunctionResponse> CREATOR =
+ new Parcelable.Creator<TvProprietaryFunctionResponse>() {
+ @Override
+ public TvProprietaryFunctionResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TvProprietaryFunctionResponse[] newArray(int size) {
+ return new TvProprietaryFunctionResponse[size];
+ }
+ };
+
+ private final String mResponse;
+
+ public static TvProprietaryFunctionResponse createFromParcelBody(Parcel in) {
+ return new TvProprietaryFunctionResponse(in);
+ }
+
+ public TvProprietaryFunctionResponse(int requestId, int sequence, int responseResult,
+ String response) {
+ super(responseType, requestId, sequence, responseResult);
+ mResponse = response;
+ }
+
+ protected TvProprietaryFunctionResponse(Parcel source) {
+ super(responseType, source);
+ mResponse = source.readString();
+ }
+
+ public String getResponse() {
+ return mResponse;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mResponse);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 73a821e..94de7fa 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -74,6 +74,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
/**
* This class is used to interact with hardware tuners devices.
@@ -248,6 +249,7 @@
private static final int FILTER_CLEANUP_THRESHOLD = 256;
+
/** @hide */
@IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
@Retention(RetentionPolicy.SOURCE)
@@ -304,6 +306,11 @@
private final Object mOnTuneEventLock = new Object();
private final Object mScanCallbackLock = new Object();
private final Object mOnResourceLostListenerLock = new Object();
+ private final ReentrantLock mFrontendLock = new ReentrantLock();
+ private final ReentrantLock mLnbLock = new ReentrantLock();
+ private final ReentrantLock mFrontendCiCamLock = new ReentrantLock();
+ private final ReentrantLock mDemuxLock = new ReentrantLock();
+ private int mRequestedCiCamId;
private Integer mDemuxHandle;
private Integer mFrontendCiCamHandle;
@@ -391,7 +398,12 @@
/** @hide */
public List<Integer> getFrontendIds() {
- return nativeGetFrontendIds();
+ mFrontendLock.lock();
+ try {
+ return nativeGetFrontendIds();
+ } finally {
+ mFrontendLock.unlock();
+ }
}
/**
@@ -426,13 +438,20 @@
* @param tuner the Tuner instance to share frontend resource with.
*/
public void shareFrontendFromTuner(@NonNull Tuner tuner) {
- mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
- synchronized (mIsSharedFrontend) {
- mFrontendHandle = tuner.mFrontendHandle;
- mFrontend = tuner.mFrontend;
- mIsSharedFrontend = true;
+ acquireTRMSLock("shareFrontendFromTuner()");
+ mFrontendLock.lock();
+ try {
+ mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
+ synchronized (mIsSharedFrontend) {
+ mFrontendHandle = tuner.mFrontendHandle;
+ mFrontend = tuner.mFrontend;
+ mIsSharedFrontend = true;
+ }
+ nativeShareFrontend(mFrontend.mId);
+ } finally {
+ releaseTRMSLock();
+ mFrontendLock.unlock();
}
- nativeShareFrontend(mFrontend.mId);
}
/**
@@ -498,39 +517,62 @@
*/
@Override
public void close() {
- releaseAll();
- TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+ acquireTRMSLock("close()");
+ try {
+ releaseAll();
+ TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+ } finally {
+ releaseTRMSLock();
+ }
}
private void releaseAll() {
- if (mFrontendHandle != null) {
- synchronized (mIsSharedFrontend) {
- if (!mIsSharedFrontend) {
- int res = nativeCloseFrontend(mFrontendHandle);
- if (res != Tuner.RESULT_SUCCESS) {
- TunerUtils.throwExceptionForResult(res, "failed to close frontend");
+ mFrontendLock.lock();
+ try {
+ if (mFrontendHandle != null) {
+ synchronized (mIsSharedFrontend) {
+ if (!mIsSharedFrontend) {
+ int res = nativeCloseFrontend(mFrontendHandle);
+ if (res != Tuner.RESULT_SUCCESS) {
+ TunerUtils.throwExceptionForResult(res, "failed to close frontend");
+ }
+ mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
}
- mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
+ mIsSharedFrontend = false;
}
- mIsSharedFrontend = false;
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
+ mFrontendHandle = null;
+ mFrontend = null;
}
- FrameworkStatsLog
- .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
- mFrontendHandle = null;
- mFrontend = null;
+ } finally {
+ mFrontendLock.unlock();
}
- if (mLnb != null) {
- mLnb.close();
- }
- if (mFrontendCiCamHandle != null) {
- int result = nativeUnlinkCiCam(mFrontendCiCamId);
- if (result == RESULT_SUCCESS) {
- mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
- mFrontendCiCamId = null;
- mFrontendCiCamHandle = null;
+
+ mLnbLock.lock();
+ try {
+ if (mLnb != null) {
+ mLnb.close();
}
+ } finally {
+ mLnbLock.unlock();
}
+
+ mFrontendCiCamLock.lock();
+ try {
+ if (mFrontendCiCamHandle != null) {
+ int result = nativeUnlinkCiCam(mFrontendCiCamId);
+ if (result == RESULT_SUCCESS) {
+ mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+ mFrontendCiCamId = null;
+ mFrontendCiCamHandle = null;
+ }
+ }
+ } finally {
+ mFrontendCiCamLock.unlock();
+ }
+
synchronized (mDescramblers) {
if (!mDescramblers.isEmpty()) {
for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
@@ -543,6 +585,7 @@
mDescramblers.clear();
}
}
+
synchronized (mFilters) {
if (!mFilters.isEmpty()) {
for (WeakReference<Filter> weakFilter : mFilters) {
@@ -554,13 +597,19 @@
mFilters.clear();
}
}
- if (mDemuxHandle != null) {
- int res = nativeCloseDemux(mDemuxHandle);
- if (res != Tuner.RESULT_SUCCESS) {
- TunerUtils.throwExceptionForResult(res, "failed to close demux");
+
+ mDemuxLock.lock();
+ try {
+ if (mDemuxHandle != null) {
+ int res = nativeCloseDemux(mDemuxHandle);
+ if (res != Tuner.RESULT_SUCCESS) {
+ TunerUtils.throwExceptionForResult(res, "failed to close demux");
+ }
+ mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
+ mDemuxHandle = null;
}
- mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
- mDemuxHandle = null;
+ } finally {
+ mDemuxLock.unlock();
}
mTunerResourceManager.unregisterClientProfile(mClientId);
@@ -763,28 +812,37 @@
*/
@Result
public int tune(@NonNull FrontendSettings settings) {
- final int type = settings.getType();
- if (mFrontendHandle != null && type != mFrontendType) {
- Log.e(TAG, "Frontend was opened with type " + mFrontendType + ", new type is " + type);
- return RESULT_INVALID_STATE;
- }
- Log.d(TAG, "Tune to " + settings.getFrequencyLong());
- mFrontendType = type;
- if (mFrontendType == FrontendSettings.TYPE_DTMB) {
- if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
+ mFrontendLock.lock();
+ try {
+ final int type = settings.getType();
+ if (mFrontendHandle != null && type != mFrontendType) {
+ Log.e(TAG, "Frontend was opened with type " + mFrontendType
+ + ", new type is " + type);
+ return RESULT_INVALID_STATE;
+ }
+ Log.d(TAG, "Tune to " + settings.getFrequencyLong());
+ mFrontendType = type;
+ if (mFrontendType == FrontendSettings.TYPE_DTMB) {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
+ return RESULT_UNAVAILABLE;
+ }
+ }
+
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+ mFrontendInfo = null;
+ Log.d(TAG, "Write Stats Log for tuning.");
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
+ int res = nativeTune(settings.getType(), settings);
+ return res;
+ } else {
return RESULT_UNAVAILABLE;
}
+ } finally {
+ mFrontendLock.unlock();
}
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- mFrontendInfo = null;
- Log.d(TAG, "Write Stats Log for tuning.");
- FrameworkStatsLog
- .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
- return nativeTune(settings.getType(), settings);
- }
- return RESULT_UNAVAILABLE;
}
/**
@@ -797,7 +855,12 @@
*/
@Result
public int cancelTuning() {
- return nativeStopTune();
+ mFrontendLock.lock();
+ try {
+ return nativeStopTune();
+ } finally {
+ mFrontendLock.unlock();
+ }
}
/**
@@ -824,33 +887,41 @@
@Result
public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
@NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
- synchronized (mScanCallbackLock) {
- // Scan can be called again for blink scan if scanCallback and executor are same as
- //before.
- if (((mScanCallback != null) && (mScanCallback != scanCallback))
- || ((mScanCallbackExecutor != null) && (mScanCallbackExecutor != executor))) {
- throw new IllegalStateException(
- "Different Scan session already in progress. stopScan must be called "
- + "before a new scan session can be " + "started.");
- }
- mFrontendType = settings.getType();
- if (mFrontendType == FrontendSettings.TYPE_DTMB) {
- if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_1_1,
- "Scan with DTMB Frontend")) {
- return RESULT_UNAVAILABLE;
+
+ mFrontendLock.lock();
+ try {
+ synchronized (mScanCallbackLock) {
+ // Scan can be called again for blink scan if scanCallback and executor are same as
+ //before.
+ if (((mScanCallback != null) && (mScanCallback != scanCallback))
+ || ((mScanCallbackExecutor != null)
+ && (mScanCallbackExecutor != executor))) {
+ throw new IllegalStateException(
+ "Different Scan session already in progress. stopScan must be called "
+ + "before a new scan session can be " + "started.");
}
+ mFrontendType = settings.getType();
+ if (mFrontendType == FrontendSettings.TYPE_DTMB) {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1,
+ "Scan with DTMB Frontend")) {
+ return RESULT_UNAVAILABLE;
+ }
+ }
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+ mFrontendLock)) {
+ mScanCallback = scanCallback;
+ mScanCallbackExecutor = executor;
+ mFrontendInfo = null;
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
+ return nativeScan(settings.getType(), settings, scanType);
+ }
+ return RESULT_UNAVAILABLE;
}
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- mScanCallback = scanCallback;
- mScanCallbackExecutor = executor;
- mFrontendInfo = null;
- FrameworkStatsLog
- .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
- return nativeScan(settings.getType(), settings, scanType);
- }
- return RESULT_UNAVAILABLE;
+ } finally {
+ mFrontendLock.unlock();
}
}
@@ -867,14 +938,19 @@
*/
@Result
public int cancelScanning() {
- synchronized (mScanCallbackLock) {
- FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
+ mFrontendLock.lock();
+ try {
+ synchronized (mScanCallbackLock) {
+ FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
- int retVal = nativeStopScan();
- mScanCallback = null;
- mScanCallbackExecutor = null;
- return retVal;
+ int retVal = nativeStopScan();
+ mScanCallback = null;
+ mScanCallbackExecutor = null;
+ return retVal;
+ }
+ } finally {
+ mFrontendLock.unlock();
}
}
@@ -903,7 +979,12 @@
*/
@Result
private int setLnb(@NonNull Lnb lnb) {
- return nativeSetLnb(lnb);
+ mLnbLock.lock();
+ try {
+ return nativeSetLnb(lnb);
+ } finally {
+ mLnbLock.unlock();
+ }
}
/**
@@ -929,10 +1010,15 @@
*/
@Nullable
public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
- if (mFrontend == null) {
- throw new IllegalStateException("frontend is not initialized");
+ mFrontendLock.lock();
+ try {
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
+ return nativeGetFrontendStatus(statusTypes);
+ } finally {
+ mFrontendLock.unlock();
}
- return nativeGetFrontendStatus(statusTypes);
}
/**
@@ -942,11 +1028,16 @@
* @return the id of hardware A/V sync.
*/
public int getAvSyncHwId(@NonNull Filter filter) {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return INVALID_AV_SYNC_ID;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return INVALID_AV_SYNC_ID;
+ }
+ Integer id = nativeGetAvSyncHwId(filter);
+ return id == null ? INVALID_AV_SYNC_ID : id;
+ } finally {
+ mDemuxLock.unlock();
}
- Integer id = nativeGetAvSyncHwId(filter);
- return id == null ? INVALID_AV_SYNC_ID : id;
}
/**
@@ -959,11 +1050,16 @@
* @return the current timestamp of hardware A/V sync.
*/
public long getAvSyncTime(int avSyncHwId) {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return INVALID_TIMESTAMP;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return INVALID_TIMESTAMP;
+ }
+ Long time = nativeGetAvSyncTime(avSyncHwId);
+ return time == null ? INVALID_TIMESTAMP : time;
+ } finally {
+ mDemuxLock.unlock();
}
- Long time = nativeGetAvSyncTime(avSyncHwId);
- return time == null ? INVALID_TIMESTAMP : time;
}
/**
@@ -980,10 +1076,15 @@
*/
@Result
public int connectCiCam(int ciCamId) {
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return nativeConnectCiCam(ciCamId);
+ mDemuxLock.lock();
+ try {
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return nativeConnectCiCam(ciCamId);
+ }
+ return RESULT_UNAVAILABLE;
+ } finally {
+ mDemuxLock.unlock();
}
- return RESULT_UNAVAILABLE;
}
/**
@@ -1011,14 +1112,30 @@
* {@link TunerVersionChecker#getTunerVersion()}.
*/
public int connectFrontendToCiCam(int ciCamId) {
- if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
- "linkFrontendToCiCam")) {
- if (checkCiCamResource(ciCamId)
- && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- return nativeLinkCiCam(ciCamId);
+ // TODO: change this so TRMS lock is held only when the resource handles for
+ // CiCam/Frontend is null. Current implementation can only handle one local lock for that.
+ acquireTRMSLock("connectFrontendToCiCam()");
+ mFrontendCiCamLock.lock();
+ mFrontendLock.lock();
+ try {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1,
+ "linkFrontendToCiCam")) {
+ mRequestedCiCamId = ciCamId;
+ // No need to unlock mFrontendCiCamLock and mFrontendLock below becauase
+ // TRMS lock is already acquired. Pass null to disable lock related operations
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, null)
+ && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, null)
+ ) {
+ return nativeLinkCiCam(ciCamId);
+ }
}
+ return INVALID_LTS_ID;
+ } finally {
+ releaseTRMSLock();
+ mFrontendCiCamLock.unlock();
+ mFrontendLock.unlock();
}
- return INVALID_LTS_ID;
}
/**
@@ -1033,10 +1150,15 @@
*/
@Result
public int disconnectCiCam() {
- if (mDemuxHandle != null) {
- return nativeDisconnectCiCam();
+ mDemuxLock.lock();
+ try {
+ if (mDemuxHandle != null) {
+ return nativeDisconnectCiCam();
+ }
+ return RESULT_UNAVAILABLE;
+ } finally {
+ mDemuxLock.unlock();
}
- return RESULT_UNAVAILABLE;
}
/**
@@ -1057,20 +1179,30 @@
*/
@Result
public int disconnectFrontendToCiCam(int ciCamId) {
- if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
- "unlinkFrontendToCiCam")) {
- if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
- && mFrontendCiCamId == ciCamId) {
- int result = nativeUnlinkCiCam(ciCamId);
- if (result == RESULT_SUCCESS) {
- mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
- mFrontendCiCamId = null;
- mFrontendCiCamHandle = null;
+ acquireTRMSLock("disconnectFrontendToCiCam()");
+ try {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1,
+ "unlinkFrontendToCiCam")) {
+ mFrontendCiCamLock.lock();
+ if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
+ && mFrontendCiCamId == ciCamId) {
+ int result = nativeUnlinkCiCam(ciCamId);
+ if (result == RESULT_SUCCESS) {
+ mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+ mFrontendCiCamId = null;
+ mFrontendCiCamHandle = null;
+ }
+ return result;
}
- return result;
}
+ return RESULT_UNAVAILABLE;
+ } finally {
+ if (mFrontendCiCamLock.isLocked()) {
+ mFrontendCiCamLock.unlock();
+ }
+ releaseTRMSLock();
}
- return RESULT_UNAVAILABLE;
}
/**
@@ -1082,16 +1214,21 @@
*/
@Nullable
public FrontendInfo getFrontendInfo() {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- return null;
+ mFrontendLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+ return null;
+ }
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
+ if (mFrontendInfo == null) {
+ mFrontendInfo = getFrontendInfoById(mFrontend.mId);
+ }
+ return mFrontendInfo;
+ } finally {
+ mFrontendLock.unlock();
}
- if (mFrontend == null) {
- throw new IllegalStateException("frontend is not initialized");
- }
- if (mFrontendInfo == null) {
- mFrontendInfo = getFrontendInfoById(mFrontend.mId);
- }
- return mFrontendInfo;
}
/**
@@ -1114,7 +1251,12 @@
/** @hide */
public FrontendInfo getFrontendInfoById(int id) {
- return nativeGetFrontendInfo(id);
+ mFrontendLock.lock();
+ try {
+ return nativeGetFrontendInfo(id);
+ } finally {
+ mFrontendLock.unlock();
+ }
}
/**
@@ -1125,7 +1267,12 @@
*/
@Nullable
public DemuxCapabilities getDemuxCapabilities() {
- return nativeGetDemuxCapabilities();
+ mDemuxLock.lock();
+ try {
+ return nativeGetDemuxCapabilities();
+ } finally {
+ mDemuxLock.unlock();
+ }
}
private void onFrontendEvent(int eventType) {
@@ -1417,32 +1564,37 @@
public Filter openFilter(@Type int mainType, @Subtype int subType,
@BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
@Nullable FilterCallback cb) {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
- }
- Filter filter = nativeOpenFilter(
- mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
- if (filter != null) {
- filter.setType(mainType, subType);
- filter.setCallback(cb, executor);
- if (mHandler == null) {
- mHandler = createEventHandler();
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
}
- synchronized (mFilters) {
- WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
- mFilters.add(weakFilter);
- if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
- Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
- while (iterator.hasNext()) {
- WeakReference<Filter> wFilter = iterator.next();
- if (wFilter.get() == null) {
- iterator.remove();
+ Filter filter = nativeOpenFilter(
+ mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
+ if (filter != null) {
+ filter.setType(mainType, subType);
+ filter.setCallback(cb, executor);
+ if (mHandler == null) {
+ mHandler = createEventHandler();
+ }
+ synchronized (mFilters) {
+ WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
+ mFilters.add(weakFilter);
+ if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
+ Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
+ while (iterator.hasNext()) {
+ WeakReference<Filter> wFilter = iterator.next();
+ if (wFilter.get() == null) {
+ iterator.remove();
+ }
}
}
}
}
+ return filter;
+ } finally {
+ mDemuxLock.unlock();
}
- return filter;
}
/**
@@ -1457,18 +1609,24 @@
*/
@Nullable
public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(cb, "LnbCallback must not be null");
- if (mLnb != null) {
- mLnb.setCallback(executor, cb, this);
- return mLnb;
+ mLnbLock.lock();
+ try {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(cb, "LnbCallback must not be null");
+ if (mLnb != null) {
+ mLnb.setCallback(executor, cb, this);
+ return mLnb;
+ }
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
+ && mLnb != null) {
+ mLnb.setCallback(executor, cb, this);
+ setLnb(mLnb);
+ return mLnb;
+ }
+ return null;
+ } finally {
+ mLnbLock.unlock();
}
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) {
- mLnb.setCallback(executor, cb, this);
- setLnb(mLnb);
- return mLnb;
- }
- return null;
}
/**
@@ -1483,20 +1641,25 @@
@Nullable
public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
@NonNull LnbCallback cb) {
- Objects.requireNonNull(name, "LNB name must not be null");
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(cb, "LnbCallback must not be null");
- Lnb newLnb = nativeOpenLnbByName(name);
- if (newLnb != null) {
- if (mLnb != null) {
- mLnb.close();
- mLnbHandle = null;
+ mLnbLock.lock();
+ try {
+ Objects.requireNonNull(name, "LNB name must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(cb, "LnbCallback must not be null");
+ Lnb newLnb = nativeOpenLnbByName(name);
+ if (newLnb != null) {
+ if (mLnb != null) {
+ mLnb.close();
+ mLnbHandle = null;
+ }
+ mLnb = newLnb;
+ mLnb.setCallback(executor, cb, this);
+ setLnb(mLnb);
}
- mLnb = newLnb;
- mLnb.setCallback(executor, cb, this);
- setLnb(mLnb);
+ return mLnb;
+ } finally {
+ mLnbLock.unlock();
}
- return mLnb;
}
private boolean requestLnb() {
@@ -1518,10 +1681,15 @@
*/
@Nullable
public TimeFilter openTimeFilter() {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ return nativeOpenTimeFilter();
+ } finally {
+ mDemuxLock.unlock();
}
- return nativeOpenTimeFilter();
}
/**
@@ -1532,10 +1700,15 @@
@RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
@Nullable
public Descrambler openDescrambler() {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ return requestDescrambler();
+ } finally {
+ mDemuxLock.unlock();
}
- return requestDescrambler();
}
/**
@@ -1553,14 +1726,19 @@
@BytesLong long bufferSize,
@CallbackExecutor @NonNull Executor executor,
@NonNull OnRecordStatusChangedListener l) {
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
+ dvr.setListener(executor, l);
+ return dvr;
+ } finally {
+ mDemuxLock.unlock();
}
- DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
- dvr.setListener(executor, l);
- return dvr;
}
/**
@@ -1578,14 +1756,19 @@
@BytesLong long bufferSize,
@CallbackExecutor @NonNull Executor executor,
@NonNull OnPlaybackStatusChangedListener l) {
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
+ dvr.setListener(executor, l);
+ return dvr;
+ } finally {
+ mDemuxLock.unlock();
}
- DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
- dvr.setListener(executor, l);
- return dvr;
}
/**
@@ -1602,6 +1785,8 @@
static public SharedFilter openSharedFilter(@NonNull Context context,
@NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor,
@NonNull SharedFilterCallback cb) {
+ // TODO: check what happenes when onReclaimResources() is called and see if
+ // this needs to be protected with TRMS lock
Objects.requireNonNull(sharedFilterToken, "sharedFilterToken must not be null");
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "SharedFilterCallback must not be null");
@@ -1665,22 +1850,28 @@
return granted;
}
- private boolean checkResource(int resourceType) {
+ private boolean checkResource(int resourceType, ReentrantLock localLock) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
- if (mFrontendHandle == null && !requestFrontend()) {
+ if (mFrontendHandle == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
- if (mLnb == null && !requestLnb()) {
+ if (mLnb == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
- if (mDemuxHandle == null && !requestDemux()) {
+ if (mDemuxHandle == null && !requestResource(resourceType, localLock)) {
+ return false;
+ }
+ break;
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
+ if (mFrontendCiCamHandle == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
@@ -1691,24 +1882,91 @@
return true;
}
- private boolean checkCiCamResource(int ciCamId) {
- if (mFrontendCiCamHandle == null && !requestFrontendCiCam(ciCamId)) {
- return false;
+ // Expected flow of how to use this function is:
+ // 1) lock the localLock and check if the resource is already held
+ // 2) if yes, no need to call this function and continue with the handle with the lock held
+ // 3) if no, then first release the held lock and grab the TRMS lock to avoid deadlock
+ // 4) grab the local lock again and release the TRMS lock
+ // If localLock is null, we'll assume the caller does not want the lock related operations
+ private boolean requestResource(int resourceType, ReentrantLock localLock) {
+ boolean enableLockOperations = localLock != null;
+
+ // release the local lock first to avoid deadlock
+ if (enableLockOperations) {
+ if (localLock.isLocked()) {
+ localLock.unlock();
+ } else {
+ throw new IllegalStateException("local lock must be locked beforehand");
+ }
}
- return true;
+
+ // now safe to grab TRMS lock
+ if (enableLockOperations) {
+ acquireTRMSLock("requestResource:" + resourceType);
+ }
+
+ try {
+ // lock the local lock
+ if (enableLockOperations) {
+ localLock.lock();
+ }
+ switch (resourceType) {
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
+ return requestFrontend();
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
+ return requestLnb();
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
+ return requestDemux();
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
+ return requestFrontendCiCam(mRequestedCiCamId);
+ }
+ default:
+ return false;
+ }
+ } finally {
+ if (enableLockOperations) {
+ releaseTRMSLock();
+ }
+ }
}
/* package */ void releaseLnb() {
- if (mLnbHandle != null) {
- // LNB handle can be null if it's opened by name.
- mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
- mLnbHandle = null;
+ acquireTRMSLock("releaseLnb()");
+ mLnbLock.lock();
+ try {
+ if (mLnbHandle != null) {
+ // LNB handle can be null if it's opened by name.
+ mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
+ mLnbHandle = null;
+ }
+ mLnb = null;
+ } finally {
+ releaseTRMSLock();
+ mLnbLock.unlock();
}
- mLnb = null;
}
/** @hide */
public int getClientId() {
return mClientId;
}
+
+ private void acquireTRMSLock(String functionNameForLog) {
+ if (DEBUG) {
+ Log.d(TAG, "ATTEMPT:acquireLock() in " + functionNameForLog
+ + "for clientId:" + mClientId);
+ }
+ if (!mTunerResourceManager.acquireLock(mClientId)) {
+ Log.e(TAG, "FAILED:acquireLock() in " + functionNameForLog
+ + " for clientId:" + mClientId + " - this can cause deadlock between"
+ + " Tuner API calls and onReclaimResources()");
+ }
+ }
+
+ private void releaseTRMSLock() {
+ mTunerResourceManager.releaseLock(mClientId);
+ }
}
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 244fd0e..fe611c7 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -320,6 +320,48 @@
}
/**
+ * Grants the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>{@link Tuner} functions that call both [@link TunerResourceManager} APIs and
+ * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()}
+ * must call this API before acquiring lock used in onReclaimResources().
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ public boolean acquireLock(int clientId) {
+ try {
+ return mService.acquireLock(clientId, Thread.currentThread().getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Releases the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>This API must be called in pair with {@link #acquireLock(int, int)}
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ public boolean releaseLock(int clientId) {
+ try {
+ return mService.releaseLock(clientId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Requests a frontend resource.
*
* <p>There are three possible scenarios:
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 7bc5058..5f35820 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -412,4 +412,34 @@
* @param resourceType The resource type to restore the map for.
*/
void restoreResourceMap(in int resourceType);
+
+ /**
+ * Grants the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>{@link Tuner} functions that call both [@link TunerResourceManager} APIs and
+ * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()}
+ * must call this API before acquiring lock used in onReclaimResources().
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ boolean acquireLock(in int clientId, in long clientThreadId);
+
+ /**
+ * Releases the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>This API must be called in pair with {@link #acquireLock(int, int)}
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ boolean releaseLock(in int clientId);
}
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 9d3ce34..cc7bb4a 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -15,6 +15,8 @@
<!-- controls -->
<uses-permission android:name="android.permission.BIND_CONTROLS" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index cb858c8..6d5615d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -109,7 +109,7 @@
a.recycle();
}
- setBackground(true);
+ setBackground(mSwitch.isChecked());
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 389892e..8ac4e38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -170,6 +170,12 @@
}
@VisibleForTesting
+ void registerIntentReceiver() {
+ mContext.registerReceiverAsUser(mBroadcastReceiver, mUserHandle, mAdapterIntentFilter,
+ null, mReceiverHandler);
+ }
+
+ @VisibleForTesting
void addProfileHandler(String action, Handler handler) {
mHandlerMap.put(action, handler);
mProfileIntentFilter.addAction(action);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 8b17be1..dee6894 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -85,6 +85,7 @@
@VisibleForTesting
protected Context mContext;
private final int mThemeResId;
+ private final boolean mCancelIsNeutral;
@VisibleForTesting
protected TextView mZenAlarmWarning;
@VisibleForTesting
@@ -101,8 +102,13 @@
}
public EnableZenModeDialog(Context context, int themeResId) {
+ this(context, themeResId, false /* cancelIsNeutral */);
+ }
+
+ public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral) {
mContext = context;
mThemeResId = themeResId;
+ mCancelIsNeutral = cancelIsNeutral;
}
public AlertDialog createDialog() {
@@ -115,7 +121,6 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId)
.setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
- .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
new DialogInterface.OnClickListener() {
@Override
@@ -145,6 +150,12 @@
}
});
+ if (mCancelIsNeutral) {
+ builder.setNeutralButton(R.string.cancel, null);
+ } else {
+ builder.setNegativeButton(R.string.cancel, null);
+ }
+
View contentView = getContentView();
bindConditions(forever());
builder.setView(contentView);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 4bff78f..bee466d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -109,7 +109,7 @@
/* handler= */ null, /* userHandle= */ null);
verify(mockContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
- eq(null), eq(null));
+ eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
}
@Test
@@ -120,7 +120,7 @@
/* handler= */ null, UserHandle.ALL);
verify(mockContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq(UserHandle.ALL),
- any(IntentFilter.class), eq(null), eq(null));
+ any(IntentFilter.class), eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
}
/**
@@ -129,6 +129,7 @@
@Test
public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
mContext.sendBroadcast(mIntent);
@@ -142,6 +143,7 @@
@Test
public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
mContext.sendBroadcast(mIntent);
@@ -167,6 +169,7 @@
@Test
public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -179,6 +182,7 @@
@Test
public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -192,6 +196,7 @@
public void dispatchAclConnectionStateChanged_aclDisconnected_shouldNotCallbackSubDevice() {
when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -205,6 +210,7 @@
public void dispatchAclConnectionStateChanged_aclConnected_shouldNotCallbackSubDevice() {
when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -218,6 +224,7 @@
public void dispatchAclConnectionStateChanged_findDeviceReturnNull_shouldNotDispatchCallback() {
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null);
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -354,6 +361,7 @@
@Test
public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -369,6 +377,7 @@
@Test
public void showUnbondMessage_reasonRemoteDeviceDown_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -385,6 +394,7 @@
@Test
public void showUnbondMessage_reasonAuthRejected_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -400,6 +410,7 @@
@Test
public void showUnbondMessage_reasonAuthFailed_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 892cd63..1e2d4e9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -25,6 +25,7 @@
import static android.provider.Settings.SET_ALL_RESULT_DISABLED;
import static android.provider.Settings.SET_ALL_RESULT_FAILURE;
import static android.provider.Settings.SET_ALL_RESULT_SUCCESS;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
@@ -3623,7 +3624,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 207;
+ private static final int SETTINGS_VERSION = 208;
private final int mUserId;
@@ -5472,6 +5473,30 @@
currentVersion = 207;
}
+ if (currentVersion == 207) {
+ // Version 207: Reset the
+ // Secure#ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT as enabled
+ // status for showing the tooltips.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting accessibilityButtonMode = secureSettings.getSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_MODE);
+ if (!accessibilityButtonMode.isNull()
+ && accessibilityButtonMode.getValue().equals(
+ String.valueOf(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU))) {
+ if (isGestureNavigateEnabled()
+ && hasValueInA11yButtonTargets(secureSettings)) {
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
+ /* enabled */ "1",
+ /* tag= */ null,
+ /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 208;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 36b633b..262cf53 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -603,6 +603,9 @@
<!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
<uses-permission android:name="android.permission.LOCK_DEVICE" />
+ <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+ <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index bcf95d8..5c19b41 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -121,6 +121,7 @@
<uses-permission android:name="android.permission.SET_ORIENTATION" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.MONITOR_INPUT" />
+ <uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
<uses-permission android:name="android.permission.INPUT_CONSUMER" />
<!-- DreamManager -->
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 4fc38a8..1601043 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -219,6 +219,9 @@
<!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] -->
<string name="kg_face_not_recognized">Not recognized</string>
+ <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
+ <string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
+
<!-- Instructions telling the user remaining times when enter SIM PIN view. -->
<plurals name="kg_password_default_pin_message">
<item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
deleted file mode 100644
index 50267fd..0000000
--- a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <stroke
- android:color="?androidprv:attr/colorAccentPrimaryVariant"
- android:width="1dp"/>
- <corners android:radius="20dp"/>
- <padding
- android:left="8dp"
- android:right="8dp"
- android:top="4dp"
- android:bottom="4dp" />
- <solid android:color="@android:color/transparent" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
deleted file mode 100644
index d9ae777..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetBottom="6dp"
- android:insetTop="6dp">
- <shape android:shape="rectangle">
- <stroke
- android:color="@color/media_dialog_outlined_button"
- android:width="1dp"/>
- <corners android:radius="20dp"/>
- <padding
- android:left="16dp"
- android:right="16dp"
- android:top="8dp"
- android:bottom="8dp"/>
- <solid android:color="@android:color/transparent"/>
- </shape>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
deleted file mode 100644
index d49454f..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetBottom="6dp"
- android:insetTop="6dp">
- <shape android:shape="rectangle">
- <stroke
- android:color="@android:color/transparent"
- android:width="1dp"/>
- <corners android:radius="20dp"/>
- <padding
- android:left="@dimen/media_output_dialog_button_padding_horizontal"
- android:right="@dimen/media_output_dialog_button_padding_horizontal"
- android:top="@dimen/media_output_dialog_button_padding_vertical"
- android:bottom="@dimen/media_output_dialog_button_padding_vertical"/>
- <solid android:color="@color/media_dialog_solid_button_background"/>
- </shape>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index 665b456..a47299d 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -29,7 +29,7 @@
<shape android:shape="rectangle">
<corners android:radius="?android:attr/buttonCornerRadius"/>
<solid android:color="@android:color/transparent"/>
- <stroke android:color="?androidprv:attr/colorAccentPrimary"
+ <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
android:width="1dp"
/>
<padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml b/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
deleted file mode 100644
index 59a31e8..0000000
--- a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <stroke
- android:color="?androidprv:attr/colorAccentPrimary"
- android:width="1dp"/>
- <corners android:radius="24dp"/>
- <padding
- android:left="16dp"
- android:right="16dp"
- android:top="8dp"
- android:bottom="8dp" />
- <solid android:color="@android:color/transparent" />
-</shape>
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 1e0ce00..46cbc25 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -18,64 +18,71 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:orientation="vertical">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:minHeight="48dp"
- android:gravity="center"
- android:inputType="textPassword"
- android:maxLength="500"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="5"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_alignParentBottom="true">
+
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4939ea2..470298e 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -22,76 +22,81 @@
android:gravity="center_horizontal"
android:elevation="@dimen/biometric_dialog_elevation">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="0dp"
- android:paddingRight="0dp"
- android:paddingTop="0dp"
- android:paddingBottom="16dp"
- android:clipToPadding="false">
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- style="@style/LockPatternContainerStyle">
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- style="@style/LockPatternStyleBiometricPrompt"/>
-
- </FrameLayout>
-
- <TextView
- android:id="@+id/error"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ android:layout_below="@id/auth_credential_header"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:paddingBottom="16dp"
+ android:paddingTop="60dp">
- </LinearLayout>
+ <FrameLayout
+ style="@style/LockPatternContainerStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ style="@style/LockPatternStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true">
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index e90a644..75fa66b 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -67,7 +67,6 @@
<ProgressBar
android:id="@+id/wifi_searching_progress"
- android:indeterminate="true"
android:layout_width="340dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -381,54 +380,44 @@
android:id="@+id/button_layout"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_marginStart="24dp"
- android:layout_marginEnd="24dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- android:layout_marginBottom="34dp"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
android:clickable="false"
android:focusable="false">
<FrameLayout
android:id="@+id/apm_layout"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:layout_gravity="start|center_vertical"
android:orientation="vertical">
<Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:text="@string/turn_off_airplane_mode"
android:ellipsize="end"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_gravity="start|center_vertical"
- android:textAppearance="@style/TextAppearance.InternetDialog"
- android:textSize="14sp"
- android:background="@drawable/internet_dialog_footer_background"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:clickable="false"/>
</FrameLayout>
<FrameLayout
android:id="@+id/done_layout"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:clickable="true"
- android:focusable="true"
android:layout_gravity="end|center_vertical"
- android:orientation="vertical">
+ android:clickable="true"
+ android:focusable="true">
<Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:text="@string/inline_done_button"
- android:ellipsize="end"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="67dp"
- android:layout_height="36dp"
- android:layout_gravity="end|center_vertical"
- android:textAppearance="@style/TextAppearance.InternetDialog"
- android:textSize="14sp"
- android:background="@drawable/internet_dialog_footer_background"
+ style="@style/Widget.Dialog.Button"
android:clickable="false"/>
</FrameLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index 8437702..b7265b9 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -41,7 +41,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
- android:background="@color/media_dialog_background"
android:orientation="vertical">
<ImageView
android:id="@+id/app_source_icon"
@@ -76,7 +75,6 @@
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@color/media_dialog_background"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
@@ -91,18 +89,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
- android:layout_marginStart="16dp"
- android:layout_marginBottom="24dp"
- android:layout_marginEnd="16dp"
- android:background="@color/media_dialog_background"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
android:orientation="horizontal">
<Button
android:id="@+id/stop"
- style="@style/MediaOutputRoundedOutlinedButton"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="0dp"
android:text="@string/keyboard_key_media_stop"
android:visibility="gone"/>
@@ -113,10 +109,9 @@
<Button
android:id="@+id/done"
- style="@style/MediaOutputRoundedButton"
+ style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="0dp"
android:text="@string/inline_done_button"/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index e43a149..f57d65a 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -27,10 +27,10 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="24dp"
- android:paddingEnd="24dp"
- android:paddingTop="26dp"
- android:paddingBottom="30dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ android:paddingTop="@dimen/dialog_top_padding"
+ android:paddingBottom="@dimen/dialog_bottom_padding"
android:orientation="vertical">
<!-- Header -->
@@ -143,10 +143,7 @@
android:layout_weight="0"
android:layout_gravity="start"
android:text="@string/cancel"
- android:textColor="?android:textColorPrimary"
- android:background="@drawable/screenrecord_button_background_outline"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="14sp"/>
+ style="@style/Widget.Dialog.Button.BorderButton" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
@@ -158,10 +155,7 @@
android:layout_weight="0"
android:layout_gravity="end"
android:text="@string/screenrecord_start"
- android:textColor="@android:color/system_neutral1_900"
- android:background="@drawable/screenrecord_button_background_solid"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="14sp"/>
+ style="@style/Widget.Dialog.Button" />
</LinearLayout>
</LinearLayout>
</ScrollView>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index c434285..9d4c2c3 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -67,10 +67,6 @@
<!-- media output dialog-->
<color name="media_dialog_background">@android:color/system_neutral1_900</color>
- <color name="media_dialog_solid_button_background">@android:color/system_accent1_100</color>
- <color name="media_dialog_solid_button_text">@android:color/system_accent2_800</color>
- <color name="media_dialog_outlined_button">@android:color/system_accent1_100</color>
- <color name="media_dialog_outlined_button_text">@android:color/system_neutral1_50</color>
<color name="media_dialog_active_item_main_content">@android:color/system_accent2_800</color>
<color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_100</color>
<color name="media_dialog_item_status">@android:color/system_accent1_100</color>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 11865a5..8cad5b3 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -174,10 +174,6 @@
<!-- media output dialog-->
<color name="media_dialog_background" android:lstar="98">@android:color/system_neutral1_100</color>
- <color name="media_dialog_solid_button_background">@android:color/system_accent1_600</color>
- <color name="media_dialog_solid_button_text">@android:color/system_neutral1_50</color>
- <color name="media_dialog_outlined_button">@android:color/system_accent1_600</color>
- <color name="media_dialog_outlined_button_text">@android:color/system_neutral1_900</color>
<color name="media_dialog_active_item_main_content">@android:color/system_accent1_900</color>
<color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_600</color>
<color name="media_dialog_item_status">@android:color/system_accent1_900</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c09658b..c7350a1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1098,8 +1098,6 @@
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
<dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
- <dimen name="media_output_dialog_button_padding_horizontal">16dp</dimen>
- <dimen name="media_output_dialog_button_padding_vertical">8dp</dimen>
<!-- Distance that the full shade transition takes in order for qs to fully transition to the
shade -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d2ea067..99508a5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2050,8 +2050,8 @@
<string name="magnification_mode_switch_click_label">Switch</string>
<!-- Accessibility floating menu strings -->
- <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] -->
- <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string>
+ <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] -->
+ <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
<!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
<!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 1a4f3e2..01f0c12 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -200,9 +200,9 @@
<style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
- <style name="TextAppearance.AuthCredential">
+ <style name="TextAppearance.AuthCredential"
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:accessibilityLiveRegion">polite</item>
- <item name="android:gravity">center_horizontal</item>
<item name="android:textAlignment">gravity</item>
<item name="android:layout_gravity">top</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -210,44 +210,57 @@
<style name="TextAppearance.AuthCredential.Title">
<item name="android:fontFamily">google-sans</item>
- <item name="android:paddingTop">12dp</item>
- <item name="android:paddingHorizontal">24dp</item>
- <item name="android:textSize">24sp</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">36sp</item>
</style>
<style name="TextAppearance.AuthCredential.Subtitle">
<item name="android:fontFamily">google-sans</item>
- <item name="android:paddingTop">8dp</item>
- <item name="android:paddingHorizontal">24dp</item>
- <item name="android:textSize">16sp</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">18sp</item>
</style>
<style name="TextAppearance.AuthCredential.Description">
<item name="android:fontFamily">google-sans</item>
- <item name="android:paddingTop">8dp</item>
- <item name="android:paddingHorizontal">24dp</item>
- <item name="android:textSize">14sp</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">16sp</item>
</style>
<style name="TextAppearance.AuthCredential.Error">
<item name="android:paddingTop">6dp</item>
+ <item name="android:paddingBottom">18dp</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/colorError</item>
+ <item name="android:gravity">center</item>
</style>
- <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
+ <style name="TextAppearance.AuthCredential.PasswordEntry">
<item name="android:gravity">center</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">?android:attr/colorForeground</item>
<item name="android:textSize">24sp</item>
</style>
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">48dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingTop">28dp</item>
+ <item name="android:paddingBottom">20dp</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
</style>
+ <style name="AuthCredentialPasswordTheme" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="colorPrimary">?android:attr/colorPrimary</item>
+ <item name="colorPrimaryDark">?android:attr/colorPrimary</item>
+ </style>
+
<style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/>
<style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
@@ -307,9 +320,8 @@
<item name="android:maxWidth">420dp</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
- <item name="android:paddingBottom">0dp</item>
- <item name="android:paddingHorizontal">44dp</item>
- <item name="android:paddingTop">0dp</item>
+ <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:paddingBottom">40dp</item>
</style>
<style name="LockPatternStyle">
@@ -454,20 +466,6 @@
<item name="android:colorBackground">@color/media_dialog_background</item>
</style>
- <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button">
- <item name="android:background">@drawable/media_output_dialog_button_background</item>
- <item name="android:textColor">@color/media_dialog_outlined_button_text</item>
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
- </style>
-
- <style name="MediaOutputRoundedButton" parent="@android:style/Widget.Material.Button">
- <item name="android:background">@drawable/media_output_dialog_solid_button_background</item>
- <item name="android:textColor">@color/media_dialog_solid_button_text</item>
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
- </style>
-
<style name="MediaOutputItemInactiveTitle">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/media_dialog_inactive_item_main_content</item>
@@ -900,13 +898,18 @@
<item name="android:textAlignment">center</item>
</style>
- <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog">
+
+ <style name="Widget" />
+ <style name="Widget.Dialog" />
+ <style name="Widget.Dialog.Button">
+ <item name="android:buttonCornerRadius">28dp</item>
<item name="android:background">@drawable/qs_dialog_btn_filled</item>
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
<item name="android:textSize">14sp</item>
<item name="android:lineHeight">20sp</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
<item name="android:stateListAnimator">@null</item>
+ <item name="android:minWidth">0dp</item>
</style>
<style name="Widget.Dialog.Button.BorderButton">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index e46b6f1..ea93a3b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -60,14 +60,12 @@
hingeAngleProvider,
screenStatusProvider,
deviceStateManager,
- mainExecutor
+ mainExecutor,
+ mainHandler
)
val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
- PhysicsBasedUnfoldTransitionProgressProvider(
- mainHandler,
- foldStateProvider
- )
+ PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
} else {
FixedTimingTransitionProgressProvider(foldStateProvider)
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 90f5998..51eae57 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.unfold.progress
-import android.os.Handler
import android.util.Log
import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation
@@ -24,9 +23,10 @@
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FOLD_UPDATE_ABORTED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -39,7 +39,6 @@
* - doesn't handle postures
*/
internal class PhysicsBasedUnfoldTransitionProgressProvider(
- private val handler: Handler,
private val foldStateProvider: FoldStateProvider
) :
UnfoldTransitionProgressProvider,
@@ -51,8 +50,6 @@
addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
}
- private val timeoutRunnable = TimeoutRunnable()
-
private var isTransitionRunning = false
private var isAnimatedCancelRunning = false
@@ -92,7 +89,7 @@
cancelTransition(endValue = 1f, animate = true)
}
}
- FOLD_UPDATE_FINISH_FULL_OPEN -> {
+ FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_ABORTED -> {
// Do not cancel if we haven't started the transition yet.
// This could happen when we fully unfolded the device before the screen
// became available. In this case we start and immediately cancel the animation
@@ -106,7 +103,11 @@
cancelTransition(endValue = 0f, animate = false)
}
FOLD_UPDATE_START_CLOSING -> {
- startTransition(startValue = 1f)
+ // The transition might be already running as the device might start closing several
+ // times before reaching an end state.
+ if (!isTransitionRunning) {
+ startTransition(startValue = 1f)
+ }
}
}
@@ -116,8 +117,6 @@
}
private fun cancelTransition(endValue: Float, animate: Boolean) {
- handler.removeCallbacks(timeoutRunnable)
-
if (isTransitionRunning && animate) {
isAnimatedCancelRunning = true
springAnimation.animateToFinalPosition(endValue)
@@ -175,8 +174,6 @@
}
springAnimation.start()
-
- handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS)
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -187,13 +184,6 @@
listeners.remove(listener)
}
- private inner class TimeoutRunnable : Runnable {
-
- override fun run() {
- cancelTransition(endValue = 1f, animate = true)
- }
- }
-
private object AnimationProgressProperty :
FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
@@ -212,7 +202,6 @@
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true
-private const val TRANSITION_TIMEOUT_MILLIS = 2000L
private const val SPRING_STIFFNESS = 200.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 35e2b30..6d9631c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,14 +15,19 @@
*/
package com.android.systemui.unfold.updates
+import android.annotation.FloatRange
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import android.util.Log
+import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
-import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import java.util.concurrent.Executor
class DeviceFoldStateProvider(
@@ -30,7 +35,8 @@
private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider,
private val deviceStateManager: DeviceStateManager,
- private val mainExecutor: Executor
+ private val mainExecutor: Executor,
+ private val handler: Handler
) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
@@ -38,9 +44,13 @@
@FoldUpdate
private var lastFoldUpdate: Int? = null
+ @FloatRange(from = 0.0, to = 180.0)
+ private var lastHingeAngle: Float = 0f
+
private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener()
private val foldStateListener = FoldStateListener(context)
+ private val timeoutRunnable = TimeoutRunnable()
private var isFolded = false
private var isUnfoldHandled = true
@@ -72,47 +82,69 @@
override val isFullyOpened: Boolean
get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
+ private val isTransitionInProgess: Boolean
+ get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
+ lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
private fun onHingeAngle(angle: Float) {
- when (lastFoldUpdate) {
- FOLD_UPDATE_FINISH_FULL_OPEN -> {
- if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) {
- lastFoldUpdate = FOLD_UPDATE_START_CLOSING
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) }
- }
- }
- FOLD_UPDATE_START_OPENING -> {
- if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
- lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
- }
- }
- FOLD_UPDATE_START_CLOSING -> {
- if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) {
- lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
- }
+ if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") }
+
+ val isClosing = angle < lastHingeAngle
+ val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
+ val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
+ if (isClosing && !closingEventDispatched && !isFullyOpened) {
+ notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
+ }
+
+ if (isTransitionInProgess) {
+ if (isFullyOpened) {
+ notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+ cancelTimeout()
+ } else {
+ // The timeout will trigger some constant time after the last angle update.
+ rescheduleAbortAnimationTimeout()
}
}
+ lastHingeAngle = angle
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
}
private inner class FoldStateListener(context: Context) :
DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
isFolded = folded
+ lastHingeAngle = FULLY_CLOSED_DEGREES
if (folded) {
- lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
hingeAngleProvider.stop()
+ notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+ cancelTimeout()
isUnfoldHandled = false
} else {
- lastFoldUpdate = FOLD_UPDATE_START_OPENING
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
+ notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+ rescheduleAbortAnimationTimeout()
hingeAngleProvider.start()
}
})
+ private fun notifyFoldUpdate(@FoldUpdate update: Int) {
+ if (DEBUG) { Log.d(TAG, stateToString(update)) }
+ outputListeners.forEach { it.onFoldUpdate(update) }
+ lastFoldUpdate = update
+ }
+
+ private fun rescheduleAbortAnimationTimeout() {
+ if (isTransitionInProgess) {
+ cancelTimeout()
+ }
+ handler.postDelayed(timeoutRunnable, ABORT_CLOSING_MILLIS)
+ }
+
+ private fun cancelTimeout() {
+ handler.removeCallbacks(timeoutRunnable)
+ }
+
private inner class ScreenStatusListener :
ScreenStatusProvider.ScreenListener {
@@ -136,7 +168,39 @@
onHingeAngle(angle)
}
}
+
+ private inner class TimeoutRunnable : Runnable {
+
+ override fun run() {
+ notifyFoldUpdate(FOLD_UPDATE_ABORTED)
+ }
+ }
}
-private const val START_CLOSING_THRESHOLD_DEGREES = 95f
-private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
+private fun stateToString(@FoldUpdate update: Int): String {
+ return when (update) {
+ FOLD_UPDATE_START_OPENING -> "START_OPENING"
+ FOLD_UPDATE_HALF_OPEN -> "HALF_OPEN"
+ FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
+ FOLD_UPDATE_ABORTED -> "ABORTED"
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
+ FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN"
+ FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN"
+ FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
+ else -> "UNKNOWN"
+ }
+}
+
+private const val TAG = "DeviceFoldProvider"
+private const val DEBUG = false
+
+/**
+ * Time after which [FOLD_UPDATE_ABORTED] is emitted following a [FOLD_UPDATE_START_CLOSING] or
+ * [FOLD_UPDATE_START_OPENING] event, if an end state is not reached.
+ */
+@VisibleForTesting
+const val ABORT_CLOSING_MILLIS = 1000L
+
+/** Threshold after which we consider the device fully unfolded. */
+@VisibleForTesting
+const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 643ece3..bffebcd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -39,6 +39,7 @@
FOLD_UPDATE_START_OPENING,
FOLD_UPDATE_HALF_OPEN,
FOLD_UPDATE_START_CLOSING,
+ FOLD_UPDATE_ABORTED,
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
FOLD_UPDATE_FINISH_HALF_OPEN,
FOLD_UPDATE_FINISH_FULL_OPEN,
@@ -51,7 +52,8 @@
const val FOLD_UPDATE_START_OPENING = 0
const val FOLD_UPDATE_HALF_OPEN = 1
const val FOLD_UPDATE_START_CLOSING = 2
-const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3
-const val FOLD_UPDATE_FINISH_HALF_OPEN = 4
-const val FOLD_UPDATE_FINISH_FULL_OPEN = 5
-const val FOLD_UPDATE_FINISH_CLOSED = 6
+const val FOLD_UPDATE_ABORTED = 3
+const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4
+const val FOLD_UPDATE_FINISH_HALF_OPEN = 5
+const val FOLD_UPDATE_FINISH_FULL_OPEN = 6
+const val FOLD_UPDATE_FINISH_CLOSED = 7
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ba67716..237ca71 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -51,6 +51,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -335,6 +336,8 @@
private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final Executor mBackgroundExecutor;
+ private SensorPrivacyManager mSensorPrivacyManager;
+ private int mFaceAuthUserId;
/**
* Short delay before restarting fingerprint authentication after a successful try. This should
@@ -1016,6 +1019,12 @@
// Error is always the end of authentication lifecycle
mFaceCancelSignal = null;
+ boolean cameraPrivacyEnabled = false;
+ if (mSensorPrivacyManager != null) {
+ cameraPrivacyEnabled = mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+ mFaceAuthUserId);
+ }
if (msgId == FaceManager.FACE_ERROR_CANCELED
&& mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
@@ -1025,7 +1034,9 @@
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
}
- if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE
+ final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE;
+
+ if (isHwUnavailable
|| msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) {
if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
mHardwareFaceUnavailableRetryCount++;
@@ -1041,6 +1052,10 @@
requireStrongAuthIfAllLockedOut();
}
+ if (isHwUnavailable && cameraPrivacyEnabled) {
+ errString = mContext.getString(R.string.kg_face_sensor_privacy_enabled);
+ }
+
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1816,6 +1831,7 @@
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mHandler = new Handler(mainLooper) {
@Override
@@ -2517,6 +2533,7 @@
// This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
+ mFaceAuthUserId = userId;
if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 29e5574..6edf2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -31,6 +31,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PointF;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -89,6 +90,7 @@
private static final String TAG = "AuthController";
private static final boolean DEBUG = true;
+ private static final int SENSOR_PRIVACY_DELAY = 500;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final CommandQueue mCommandQueue;
@@ -122,6 +124,7 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+ private SensorPrivacyManager mSensorPrivacyManager;
private class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -492,6 +495,7 @@
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mBroadcastReceiver, filter);
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
}
private void updateFingerprintLocation() {
@@ -642,10 +646,16 @@
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+ boolean isCameraPrivacyEnabled = false;
+ if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE
+ && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+ mCurrentDialogArgs.argi1 /* userId */)) {
+ isCameraPrivacyEnabled = true;
+ }
// TODO(b/141025588): Create separate methods for handling hard and soft errors.
final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
- || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
-
+ || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT
+ || isCameraPrivacyEnabled);
if (mCurrentDialog != null) {
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
@@ -655,12 +665,23 @@
? mContext.getString(R.string.biometric_not_recognized)
: getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
- mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
+ // The camera privacy error can return before the prompt initializes its state,
+ // causing the prompt to appear to endlessly authenticate. Add a small delay
+ // to stop this.
+ if (isCameraPrivacyEnabled) {
+ mHandler.postDelayed(() -> {
+ mCurrentDialog.onAuthenticationFailed(modality,
+ mContext.getString(R.string.face_sensor_privacy_enabled));
+ }, SENSOR_PRIVACY_DELAY);
+ } else {
+ mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
+ }
} else {
final String errorMessage = getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
mCurrentDialog.onError(modality, errorMessage);
}
+
} else {
Log.w(TAG, "onBiometricError callback but dialog is gone");
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index f74fbf4..7f5744c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -85,7 +85,9 @@
public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
super(context, R.style.Theme_SystemUI_Dialog_Media);
- mContext = context;
+
+ // Save the context that is wrapped with our theme.
+ mContext = getContext();
mMediaOutputController = mediaOutputController;
mLayoutManager = new LinearLayoutManager(mContext);
mListMaxHeight = context.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7ba9cc2..a2577d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -98,7 +98,7 @@
toggleDataSaver();
Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
});
- dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
+ dialog.setNeutralButton(com.android.internal.R.string.cancel, null);
dialog.setShowForAllUsers(true);
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 83506b2..bb27458 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -190,7 +190,6 @@
case Settings.Secure.ZEN_DURATION_PROMPT:
mUiHandler.post(() -> {
Dialog dialog = makeZenModeDialog();
- SystemUIDialog.registerDismissListener(dialog);
if (view != null) {
mDialogLaunchAnimator.showFromView(dialog, view, false);
} else {
@@ -211,10 +210,12 @@
}
private Dialog makeZenModeDialog() {
- AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog)
- .createDialog();
+ AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
+ true /* cancelIsNeutral */).createDialog();
SystemUIDialog.applyFlags(dialog);
SystemUIDialog.setShowForAllUsers(dialog, true);
+ SystemUIDialog.registerDismissListener(dialog);
+ SystemUIDialog.setDialogSize(dialog);
return dialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 26c89ff..e3f085c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -78,7 +78,7 @@
private static final String TAG = "InternetDialog";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- static final long PROGRESS_DELAY_MS = 2000L;
+ static final long PROGRESS_DELAY_MS = 1500L;
private final Handler mHandler;
private final Executor mBackgroundExecutor;
@@ -137,6 +137,8 @@
protected WifiEntry mConnectedWifiEntry;
@VisibleForTesting
protected int mWifiEntriesCount;
+ @VisibleForTesting
+ protected boolean mHasMoreEntry;
// Wi-Fi scanning progress bar
protected boolean mIsProgressBarVisible;
@@ -157,7 +159,9 @@
if (DEBUG) {
Log.d(TAG, "Init InternetDialog");
}
- mContext = context;
+
+ // Save the context that is wrapped with our theme.
+ mContext = getContext();
mHandler = handler;
mBackgroundExecutor = executor;
mInternetDialogFactory = internetDialogFactory;
@@ -464,8 +468,7 @@
}
mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount());
mWifiRecyclerView.setVisibility(View.VISIBLE);
- final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0;
- mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE);
+ mSeeAllLayout.setVisibility(mHasMoreEntry ? View.VISIBLE : View.INVISIBLE);
}
@VisibleForTesting
@@ -549,9 +552,13 @@
}
private void setProgressBarVisible(boolean visible) {
+ if (mIsProgressBarVisible == visible) {
+ return;
+ }
mIsProgressBarVisible = visible;
- mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
- mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
+ mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mProgressBar.setIndeterminate(visible);
+ mDivider.setVisibility(visible ? View.GONE : View.VISIBLE);
mInternetDialogSubTitle.setText(getSubtitleText());
}
@@ -651,13 +658,14 @@
@Override
@WorkerThread
public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
- @Nullable WifiEntry connectedEntry) {
+ @Nullable WifiEntry connectedEntry, boolean hasMoreEntry) {
// Should update the carrier network layout when it is connected under airplane mode ON.
boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE
&& mInternetDialogController.isAirplaneModeEnabled();
mHandler.post(() -> {
mConnectedWifiEntry = connectedEntry;
mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+ mHasMoreEntry = hasMoreEntry;
updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */);
mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
mAdapter.notifyDataSetChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 6f63a08..1fee1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -348,6 +348,10 @@
return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
}
+ if (isCarrierNetworkActive()) {
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
// Sub-Title:
// show non_carrier_network_unavailable
// - while Wi-Fi on + no Wi-Fi item
@@ -879,20 +883,25 @@
mConnectedEntry = null;
mWifiEntriesCount = 0;
if (mCallback != null) {
- mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */,
+ false /* hasMoreEntry */);
}
return;
}
+ boolean hasMoreEntry = false;
int count = MAX_WIFI_ENTRY_COUNT;
if (mHasEthernet) {
count -= 1;
}
- if (hasActiveSubId()) {
+ if (hasActiveSubId() || isCarrierNetworkActive()) {
count -= 1;
}
- if (count > accessPoints.size()) {
- count = accessPoints.size();
+ final int wifiTotalCount = accessPoints.size();
+ if (count > wifiTotalCount) {
+ count = wifiTotalCount;
+ } else if (count < wifiTotalCount) {
+ hasMoreEntry = true;
}
WifiEntry connectedEntry = null;
@@ -909,7 +918,7 @@
mWifiEntriesCount = wifiEntries.size();
if (mCallback != null) {
- mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
+ mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry);
}
}
@@ -1060,7 +1069,7 @@
void dismissDialog();
void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
- @Nullable WifiEntry connectedEntry);
+ @Nullable WifiEntry connectedEntry, boolean hasMoreEntry);
}
void makeOverlayToast(int stringId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 5437ce6..d6125ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -25,7 +25,6 @@
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -35,9 +34,13 @@
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.time.SystemClock;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Executor;
/**
* This class handles listening to notification updates and passing them along to
@@ -47,23 +50,31 @@
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
private static final boolean DEBUG = StatusBar.DEBUG;
+ private static final long MAX_RANKING_DELAY_MILLIS = 500L;
private final Context mContext;
private final NotificationManager mNotificationManager;
- private final Handler mMainHandler;
+ private final SystemClock mSystemClock;
+ private final Executor mMainExecutor;
private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
+ private final Deque<RankingMap> mRankingMapQueue = new ConcurrentLinkedDeque<>();
+ private final Runnable mDispatchRankingUpdateRunnable = this::dispatchRankingUpdate;
+ private long mSkippingRankingUpdatesSince = -1;
+
/**
* Injected constructor. See {@link StatusBarModule}.
*/
public NotificationListener(
Context context,
NotificationManager notificationManager,
- @Main Handler mainHandler) {
+ SystemClock systemClock,
+ @Main Executor mainExecutor) {
mContext = context;
mNotificationManager = notificationManager;
- mMainHandler = mainHandler;
+ mSystemClock = systemClock;
+ mMainExecutor = mainExecutor;
}
/** Registers a listener that's notified when notifications are added/removed/etc. */
@@ -89,7 +100,7 @@
return;
}
final RankingMap currentRanking = getCurrentRanking();
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
// There's currently a race condition between the calls to getActiveNotifications() and
// getCurrentRanking(). It's possible for the ranking that we store here to not contain
// entries for every notification in getActiveNotifications(). To prevent downstream
@@ -119,7 +130,7 @@
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
for (NotificationHandler handler : mNotificationHandlers) {
@@ -134,7 +145,7 @@
int reason) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationRemoved(sbn, rankingMap, reason);
}
@@ -151,12 +162,49 @@
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
if (rankingMap != null) {
+ // Add the ranking to the queue, then run dispatchRankingUpdate() on the main thread
RankingMap r = onPluginRankingUpdate(rankingMap);
- mMainHandler.post(() -> {
- for (NotificationHandler handler : mNotificationHandlers) {
- handler.onNotificationRankingUpdate(r);
+ mRankingMapQueue.addLast(r);
+ // Maintaining our own queue and always posting the runnable allows us to guarantee the
+ // relative ordering of all events which are dispatched, which is important so that the
+ // RankingMap always has exactly the same elements that are current, per add/remove
+ // events.
+ mMainExecutor.execute(mDispatchRankingUpdateRunnable);
+ }
+ }
+
+ /**
+ * This method is (and must be) the sole consumer of the RankingMap queue. After pulling an
+ * object off the queue, it checks if the queue is empty, and only dispatches the ranking update
+ * if the queue is still empty.
+ */
+ private void dispatchRankingUpdate() {
+ if (DEBUG) Log.d(TAG, "dispatchRankingUpdate");
+ RankingMap r = mRankingMapQueue.pollFirst();
+ if (r == null) {
+ Log.wtf(TAG, "mRankingMapQueue was empty!");
+ }
+ if (!mRankingMapQueue.isEmpty()) {
+ final long now = mSystemClock.elapsedRealtime();
+ if (mSkippingRankingUpdatesSince == -1) {
+ mSkippingRankingUpdatesSince = now;
+ }
+ final long timeSkippingRankingUpdates = now - mSkippingRankingUpdatesSince;
+ if (timeSkippingRankingUpdates < MAX_RANKING_DELAY_MILLIS) {
+ if (DEBUG) {
+ Log.d(TAG, "Skipping dispatch of onNotificationRankingUpdate() -- "
+ + mRankingMapQueue.size() + " more updates already in the queue.");
}
- });
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Proceeding with dispatch of onNotificationRankingUpdate() -- "
+ + mRankingMapQueue.size() + " more updates already in the queue.");
+ }
+ }
+ mSkippingRankingUpdatesSince = -1;
+ for (NotificationHandler handler : mNotificationHandlers) {
+ handler.onNotificationRankingUpdate(r);
}
}
@@ -165,7 +213,7 @@
String pkgName, UserHandle user, NotificationChannel channel, int modificationType) {
if (DEBUG) Log.d(TAG, "onNotificationChannelModified");
if (!onPluginNotificationChannelModified(pkgName, user, channel, modificationType)) {
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationChannelModified(pkgName, user, channel, modificationType);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
index c4fadff..4551807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
@@ -40,7 +40,7 @@
super(context);
setTitle(R.string.user_remove_user_title);
setMessage(context.getString(R.string.user_remove_user_message));
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.user_remove_user_remove), this);
@@ -51,7 +51,7 @@
@Override
public void onClick(DialogInterface dialog, int which) {
- if (which == BUTTON_NEGATIVE) {
+ if (which == BUTTON_NEUTRAL) {
cancel();
} else {
dismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 8e2f88c..d574cda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -168,9 +168,10 @@
static NotificationListener provideNotificationListener(
Context context,
NotificationManager notificationManager,
- @Main Handler mainHandler) {
+ SystemClock systemClock,
+ @Main Executor mainExecutor) {
return new NotificationListener(
- context, notificationManager, mainHandler);
+ context, notificationManager, systemClock, mainExecutor);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index 4651e8446..c68d39b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -66,11 +66,7 @@
@Override
public RankingMap getCurrentRanking() {
- RankingMap currentRanking = super.getCurrentRanking();
- for (NotificationListenerController plugin : mPlugins) {
- currentRanking = plugin.getCurrentRanking(currentRanking);
- }
- return currentRanking;
+ return onPluginRankingUpdate(super.getCurrentRanking());
}
public void onPluginConnected() {
@@ -120,8 +116,11 @@
return false;
}
- public RankingMap onPluginRankingUpdate(RankingMap rankingMap) {
- return getCurrentRanking();
+ protected RankingMap onPluginRankingUpdate(RankingMap rankingMap) {
+ for (NotificationListenerController plugin : mPlugins) {
+ rankingMap = plugin.getCurrentRanking(rankingMap);
+ }
+ return rankingMap;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 43264b6..8df7b45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -126,30 +126,7 @@
* the device configuration changes, and the result will be used to resize this dialog window.
*/
protected int getWidth() {
- boolean isOnTablet =
- mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
- if (!isOnTablet) {
- return ViewGroup.LayoutParams.MATCH_PARENT;
- }
-
- int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
- if (flagValue == -1) {
- // The width of bottom sheets (624dp).
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
- mContext.getResources().getDisplayMetrics()));
- } else if (flagValue == -2) {
- // The suggested small width for all dialogs (348dp)
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
- mContext.getResources().getDisplayMetrics()));
- } else if (flagValue > 0) {
- // Any given width.
- return Math.round(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
- mContext.getResources().getDisplayMetrics()));
- } else {
- // By default we use the same width as the notification shade in portrait mode (504dp).
- return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
- }
+ return getDefaultDialogWidth(mContext);
}
/**
@@ -157,7 +134,7 @@
* the device configuration changes, and the result will be used to resize this dialog window.
*/
protected int getHeight() {
- return ViewGroup.LayoutParams.WRAP_CONTENT;
+ return getDefaultDialogHeight();
}
@Override
@@ -267,6 +244,45 @@
dismissReceiver.register();
}
+ /** Set an appropriate size to {@code dialog} depending on the current configuration. */
+ public static void setDialogSize(Dialog dialog) {
+ // We need to create the dialog first, otherwise the size will be overridden when it is
+ // created.
+ dialog.create();
+ dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()),
+ getDefaultDialogHeight());
+ }
+
+ private static int getDefaultDialogWidth(Context context) {
+ boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ if (!isOnTablet) {
+ return ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
+ if (flagValue == -1) {
+ // The width of bottom sheets (624dp).
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
+ context.getResources().getDisplayMetrics()));
+ } else if (flagValue == -2) {
+ // The suggested small width for all dialogs (348dp)
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
+ context.getResources().getDisplayMetrics()));
+ } else if (flagValue > 0) {
+ // Any given width.
+ return Math.round(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
+ context.getResources().getDisplayMetrics()));
+ } else {
+ // By default we use the same width as the notification shade in portrait mode (504dp).
+ return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+ }
+ }
+
+ private static int getDefaultDialogHeight() {
+ return ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+
private static class DismissReceiver extends BroadcastReceiver {
private static final IntentFilter INTENT_FILTER = new IntentFilter();
static {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 17dd26a..dc8dc99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1161,7 +1161,7 @@
? com.android.settingslib.R.string.guest_reset_guest_dialog_title
: R.string.guest_exit_guest_dialog_title);
setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(mGuestUserAutoCreated
@@ -1180,7 +1180,7 @@
if (mFalsingManager.isFalseTap(penalty)) {
return;
}
- if (which == BUTTON_NEGATIVE) {
+ if (which == BUTTON_NEUTRAL) {
cancel();
} else {
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
@@ -1198,7 +1198,7 @@
super(context);
setTitle(R.string.user_add_user_title);
setMessage(context.getString(R.string.user_add_user_message_short));
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok), this);
@@ -1212,7 +1212,7 @@
if (mFalsingManager.isFalseTap(penalty)) {
return;
}
- if (which == BUTTON_NEGATIVE) {
+ if (which == BUTTON_NEUTRAL) {
cancel();
} else {
mDialogLaunchAnimator.dismissStack(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 8ea7d62..fb6861d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.tv;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -38,6 +39,10 @@
@SysUISingleton
public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks {
+ private static final String ACTION_SHOW_PIP_MENU =
+ "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+ private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
private final CommandQueue mCommandQueue;
private final Lazy<AssistManager> mAssistManagerLazy;
@@ -65,4 +70,9 @@
public void startAssist(Bundle args) {
mAssistManagerLazy.get().startAssist(args);
}
+
+ @Override
+ public void showPictureInPictureMenu() {
+ mContext.sendBroadcast(new Intent(ACTION_SHOW_PIP_MENU), SYSTEMUI_PERMISSION);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 95e7a98c..6dca2a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -159,7 +159,6 @@
mAccessPoints.add(mWifiEntry1);
when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
- when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
.thenReturn(mSystemUIToast);
when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -335,6 +334,17 @@
}
@Test
+ public void getSubtitleText_withCarrierNetworkActiveOnly_returnNoOtherAvailable() {
+ fakeAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+ when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+ }
+
+ @Test
public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
}
@@ -400,7 +410,7 @@
mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
- verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any());
+ verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any(), anyBoolean());
}
@Test
@@ -409,8 +419,8 @@
mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(null /* wifiEntries */,
+ null /* connectedEntry */, false /* hasMoreEntry */);
}
@Test
@@ -423,7 +433,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.clear();
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
}
@Test
@@ -437,8 +448,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, false /* hasMoreEntry */);
}
@Test
@@ -453,7 +464,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
}
@Test
@@ -470,7 +482,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
mWifiEntries.add(mWifiEntry2);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
}
@Test
@@ -489,7 +502,8 @@
mWifiEntries.add(mWifiEntry1);
mWifiEntries.add(mWifiEntry2);
mWifiEntries.add(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -498,7 +512,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ true /* hasMoreEntry */);
}
@Test
@@ -518,7 +533,8 @@
mWifiEntries.add(mWifiEntry1);
mWifiEntries.add(mWifiEntry2);
mWifiEntries.add(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ true /* hasMoreEntry */);
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -527,7 +543,38 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ true /* hasMoreEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneCarrierWifiAndFourOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ fakeAirplaneModeEnabled(true);
+ when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
+
+ // Turn off airplane mode to has carrier WiFi, then Wi-Fi entries will keep the same.
+ reset(mInternetDialogCallback);
+ fakeAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
}
@Test
@@ -547,8 +594,8 @@
mWifiEntries.add(mWifiEntry2);
mWifiEntries.add(mWifiEntry3);
mWifiEntries.add(mWifiEntry4);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, false /* hasMoreEntry */);
// If the Ethernet exists, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -557,8 +604,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry4);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -567,8 +614,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry3);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
}
@Test
@@ -584,8 +631,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, false /* hasMoreEntry */);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 0cf063f..651bcde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -316,6 +316,20 @@
}
@Test
+ public void updateDialog_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.mConnectedWifiEntry = null;
+ mInternetDialog.mWifiEntriesCount = 1;
+
+ mInternetDialog.updateDialog(false);
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ // Show a blank block to fix the dialog height even if there is no WiFi list
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
mInternetDialog.mWifiEntriesCount = 0;
@@ -325,13 +339,15 @@
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
// Show a blank block to fix the dialog height even if there is no WiFi list
assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
- public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+ public void updateDialog_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
mInternetDialog.mConnectedWifiEntry = null;
+ mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT;
+ mInternetDialog.mHasMoreEntry = true;
mInternetDialog.updateDialog(false);
@@ -343,6 +359,8 @@
@Test
public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
+ mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1;
+ mInternetDialog.mHasMoreEntry = true;
mInternetDialog.updateDialog(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index afbe668..8c5f04f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -16,27 +16,30 @@
package com.android.systemui.statusbar;
-import static org.mockito.ArgumentMatchers.any;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationManager;
-import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -46,7 +49,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class NotificationListenerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
@@ -54,6 +56,8 @@
@Mock private NotificationHandler mNotificationHandler;
@Mock private NotificationManager mNotificationManager;
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
private NotificationListener mListener;
private StatusBarNotification mSbn;
private RankingMap mRanking = new RankingMap(new Ranking[0]);
@@ -65,7 +69,8 @@
mListener = new NotificationListener(
mContext,
mNotificationManager,
- new Handler(TestableLooper.get(this).getLooper()));
+ mFakeSystemClock,
+ mFakeExecutor);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
@@ -75,23 +80,67 @@
@Test
public void testNotificationAddCallsAddNotification() {
mListener.onNotificationPosted(mSbn, mRanking);
- TestableLooper.get(this).processAllMessages();
+ mFakeExecutor.runAllReady();
verify(mNotificationHandler).onNotificationPosted(mSbn, mRanking);
}
@Test
public void testNotificationRemovalCallsRemoveNotification() {
mListener.onNotificationRemoved(mSbn, mRanking);
- TestableLooper.get(this).processAllMessages();
+ mFakeExecutor.runAllReady();
verify(mNotificationHandler).onNotificationRemoved(eq(mSbn), eq(mRanking), anyInt());
}
@Test
public void testRankingUpdateCallsNotificationRankingUpdate() {
mListener.onNotificationRankingUpdate(mRanking);
- TestableLooper.get(this).processAllMessages();
- // RankingMap may be modified by plugins.
- verify(mNotificationHandler).onNotificationRankingUpdate(any());
+ assertThat(mFakeExecutor.runAllReady()).isEqualTo(1);
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(mRanking));
+ }
+
+ @Test
+ public void testRankingUpdateMultipleTimesCallsNotificationRankingUpdateOnce() {
+ // GIVEN multiple notification ranking updates
+ RankingMap ranking1 = mock(RankingMap.class);
+ RankingMap ranking2 = mock(RankingMap.class);
+ RankingMap ranking3 = mock(RankingMap.class);
+ mListener.onNotificationRankingUpdate(ranking1);
+ mListener.onNotificationRankingUpdate(ranking2);
+ mListener.onNotificationRankingUpdate(ranking3);
+
+ // WHEN executor runs with multiple updates in the queue
+ assertThat(mFakeExecutor.numPending()).isEqualTo(3);
+ assertThat(mFakeExecutor.runAllReady()).isEqualTo(3);
+
+ // VERIFY that only the last ranking actually gets handled
+ verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking1));
+ verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking2));
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
+ verifyNoMoreInteractions(mNotificationHandler);
+ }
+
+ @Test
+ public void testRankingUpdateWillCallAgainIfQueueIsSlow() {
+ // GIVEN multiple notification ranking updates
+ RankingMap ranking1 = mock(RankingMap.class);
+ RankingMap ranking2 = mock(RankingMap.class);
+ RankingMap ranking3 = mock(RankingMap.class);
+ mListener.onNotificationRankingUpdate(ranking1);
+ mListener.onNotificationRankingUpdate(ranking2);
+ mListener.onNotificationRankingUpdate(ranking3);
+
+ // WHEN executor runs with a 1-second gap between handling events 1 and 2
+ assertThat(mFakeExecutor.numPending()).isEqualTo(3);
+ assertThat(mFakeExecutor.runNextReady()).isTrue();
+ // delay a second, which empties the executor
+ mFakeSystemClock.advanceTime(1000);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
+
+ // VERIFY that both event 2 and event 3 are called
+ verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking1));
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking2));
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
+ verifyNoMoreInteractions(mNotificationHandler);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index a1d9a7b..be1720d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -18,7 +18,9 @@
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
import android.testing.AndroidTestingRunner
+import androidx.core.util.Consumer
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
@@ -31,9 +33,12 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import java.lang.Exception
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -48,16 +53,28 @@
@Mock
private lateinit var deviceStateManager: DeviceStateManager
- private lateinit var foldStateProvider: FoldStateProvider
+ @Mock
+ private lateinit var handler: Handler
+
+ @Captor
+ private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+ @Captor
+ private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
+
+ @Captor
+ private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
+
+ private lateinit var foldStateProvider: DeviceFoldStateProvider
private val foldUpdates: MutableList<Int> = arrayListOf()
private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
- private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
private var foldedDeviceState: Int = 0
private var unfoldedDeviceState: Int = 0
- private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java)
+ private var scheduledRunnable: Runnable? = null
+ private var scheduledRunnableDelay: Long? = null
@Before
fun setUp() {
@@ -75,7 +92,8 @@
hingeAngleProvider,
screenStatusProvider,
deviceStateManager,
- context.mainExecutor
+ context.mainExecutor,
+ handler
)
foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
@@ -91,6 +109,22 @@
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
+ verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
+
+ whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
+ scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
+ scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
+ null
+ }
+
+ whenever(handler.removeCallbacks(any<Runnable>())).then { invocationOnMock ->
+ val removedRunnable = invocationOnMock.getArgument<Runnable>(0)
+ if (removedRunnable == scheduledRunnable) {
+ scheduledRunnableDelay = null
+ scheduledRunnable = null
+ }
+ null
+ }
}
@Test
@@ -167,6 +201,86 @@
assertThat(foldUpdates).isEmpty()
}
+ @Test
+ fun startClosingEvent_afterTimeout_abortEmitted() {
+ sendHingeAngleEvent(90)
+ sendHingeAngleEvent(80)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+ }
+
+ @Test
+ fun startClosingEvent_beforeTimeout_abortNotEmitted() {
+ sendHingeAngleEvent(90)
+ sendHingeAngleEvent(80)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
+ sendHingeAngleEvent(180)
+ sendHingeAngleEvent(90)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+ sendHingeAngleEvent(80)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
+ sendHingeAngleEvent(180)
+ sendHingeAngleEvent(90)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS - 1) // The timeout should not trigger here.
+ sendHingeAngleEvent(80)
+ simulateTimeout(ABORT_CLOSING_MILLIS) // The timeout should trigger here.
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+ }
+
+ @Test
+ fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
+ sendHingeAngleEvent(180)
+
+ sendHingeAngleEvent(90)
+ sendHingeAngleEvent(80)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
+ val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
+ for (i in 1..maxAngle) {
+ foldUpdates.clear()
+
+ simulateFolding(startAngle = i)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ simulateTimeout() // Timeout to set the state to aborted.
+ }
+ }
+
+ private fun simulateTimeout(waitTime: Long = ABORT_CLOSING_MILLIS) {
+ val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
+ if (waitTime >= runnableDelay) {
+ scheduledRunnable?.run()
+ scheduledRunnable = null
+ scheduledRunnableDelay = null
+ }
+ }
+
+ private fun simulateFolding(startAngle: Int) {
+ sendHingeAngleEvent(startAngle)
+ sendHingeAngleEvent(startAngle - 1)
+ }
+
private fun setFoldState(folded: Boolean) {
val state = if (folded) foldedDeviceState else unfoldedDeviceState
foldStateListenerCaptor.value.onStateChanged(state)
@@ -175,4 +289,8 @@
private fun fireScreenOnEvent() {
screenOnListenerCaptor.value.onScreenTurnedOn()
}
+
+ private fun sendHingeAngleEvent(angle: Int) {
+ hingeAngleCaptor.value.accept(angle.toFloat())
+ }
}
diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index 594140e..21a22f4 100644
--- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -39,6 +39,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.Preconditions;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.OnTransportRegisteredListener;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportConnectionManager;
@@ -641,7 +642,7 @@
TransportConnection transportConnection =
mTransportConnectionManager.getTransportClient(
transportComponent, extras, callerLogString);
- final IBackupTransport transport;
+ final BackupTransportClient transport;
try {
transport = transportConnection.connectOrThrow(callerLogString);
} catch (TransportNotAvailableException e) {
@@ -653,10 +654,6 @@
int result;
try {
- // This is a temporary fix to allow blocking calls.
- // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking.
- Binder.allowBlocking(transport.asBinder());
-
String transportName = transport.name();
String transportDirName = transport.transportDirName();
registerTransport(transportComponent, transport);
@@ -674,8 +671,8 @@
}
/** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
- private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
- throws RemoteException {
+ private void registerTransport(ComponentName transportComponent,
+ BackupTransportClient transport) throws RemoteException {
checkCanUseTransport();
TransportDescription description =
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index a3f6eb6..85ab48c 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -21,6 +21,7 @@
import android.app.backup.RestoreSet;
import android.content.Intent;
import android.content.pm.PackageInfo;
+import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -35,6 +36,10 @@
BackupTransportClient(IBackupTransport transportBinder) {
mTransportBinder = transportBinder;
+
+ // This is a temporary fix to allow blocking calls.
+ // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking.
+ Binder.allowBlocking(mTransportBinder.asBinder());
}
/**
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
index da77eba..f9a3c36 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
@@ -59,7 +59,7 @@
import java.util.concurrent.ExecutionException;
/**
- * A {@link TransportConnection} manages the connection to an {@link IBackupTransport} service,
+ * A {@link TransportConnection} manages the connection to a {@link BackupTransportClient},
* obtained via the {@param bindIntent} parameter provided in the constructor. A
* {@link TransportConnection} is responsible for only one connection to the transport service,
* not more.
@@ -67,9 +67,9 @@
* <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
* call either {@link #connect(String)}, if you can block your thread, or {@link
* #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
- * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
- * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
- * via {@link TransportManager#disposeOfTransportClient(TransportConnection, String)}.
+ * BackupTransportClient} instance. It's meant to be passed around as a token to a connected
+ * transport. When the connection is not needed anymore you should call {@link #unbind(String)} or
+ * indirectly via {@link TransportManager#disposeOfTransportClient(TransportConnection, String)}.
*
* <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
*
@@ -106,7 +106,7 @@
private int mState = State.IDLE;
@GuardedBy("mStateLock")
- private volatile IBackupTransport mTransport;
+ private volatile BackupTransportClient mTransport;
TransportConnection(
@UserIdInt int userId,
@@ -174,10 +174,12 @@
* trigger another one, just piggyback on the original request.
*
* <p>It's guaranteed that you are going to get a call back to {@param listener} after this
- * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
- * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
- * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
- * The reasons for a null transport binder are:
+ * call. However, the {@link BackupTransportClient} parameter in
+ * {@link TransportConnectionListener#onTransportConnectionResult(BackupTransportClient,
+ * TransportConnection)}, the transport client, is not guaranteed to be non-null, or if it's
+ * non-null it's not guaranteed to be usable - i.e. it can throw {@link DeadObjectException}s
+ * on method calls. You should check for both in your code. The reasons for a null transport
+ * client are:
*
* <ul>
* <li>Some code called {@link #unbind(String)} before you got a callback.
@@ -193,7 +195,7 @@
* For unusable transport binders check {@link DeadObjectException}.
*
* @param listener The listener that will be called with the (possibly null or unusable) {@link
- * IBackupTransport} instance and this {@link TransportConnection} object.
+ * BackupTransportClient} instance and this {@link TransportConnection} object.
* @param caller A {@link String} identifying the caller for logging/debugging purposes. This
* should be a human-readable short string that is easily identifiable in the logs. Ideally
* TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
@@ -293,8 +295,8 @@
*
* <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
* same observations about state are valid here. Also, what was said about the {@link
- * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
- * value of this method.
+ * BackupTransportClient} parameter of {@link TransportConnectionListener} now apply to the
+ * return value of this method.
*
* <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
* threads. You can't call this from the process main-thread (it throws an exception if you do
@@ -305,18 +307,18 @@
*
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
- * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
- * still be unusable - throws {@link DeadObjectException} on method calls
+ * @return A {@link BackupTransportClient} transport client instance or null. If it's non-null
+ * it can still be unusable - throws {@link DeadObjectException} on method calls
*/
@WorkerThread
@Nullable
- public IBackupTransport connect(String caller) {
+ public BackupTransportClient connect(String caller) {
// If called on the main-thread this could deadlock waiting because calls to
// ServiceConnection are on the main-thread as well
Preconditions.checkState(
!Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
- IBackupTransport transport = mTransport;
+ BackupTransportClient transport = mTransport;
if (transport != null) {
log(Priority.DEBUG, caller, "Sync connect: reusing transport");
return transport;
@@ -330,7 +332,7 @@
}
}
- CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+ CompletableFuture<BackupTransportClient> transportFuture = new CompletableFuture<>();
TransportConnectionListener requestListener =
(requestedTransport, transportClient) ->
transportFuture.complete(requestedTransport);
@@ -359,13 +361,14 @@
*
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
- * @return A {@link IBackupTransport} transport binder instance.
+ * @return A {@link BackupTransportClient} transport binder instance.
* @see #connect(String)
* @throws TransportNotAvailableException if connection attempt fails.
*/
@WorkerThread
- public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
- IBackupTransport transport = connect(caller);
+ public BackupTransportClient connectOrThrow(String caller)
+ throws TransportNotAvailableException {
+ BackupTransportClient transport = connect(caller);
if (transport == null) {
log(Priority.ERROR, caller, "Transport connection failed");
throw new TransportNotAvailableException();
@@ -379,12 +382,12 @@
*
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
- * @return A {@link IBackupTransport} transport binder instance.
+ * @return A {@link BackupTransportClient} transport client instance.
* @throws TransportNotAvailableException if not connected.
*/
- public IBackupTransport getConnectedTransport(String caller)
+ public BackupTransportClient getConnectedTransport(String caller)
throws TransportNotAvailableException {
- IBackupTransport transport = mTransport;
+ BackupTransportClient transport = mTransport;
if (transport == null) {
log(Priority.ERROR, caller, "Transport not connected");
throw new TransportNotAvailableException();
@@ -425,7 +428,8 @@
}
private void onServiceConnected(IBinder binder) {
- IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+ IBackupTransport transportBinder = IBackupTransport.Stub.asInterface(binder);
+ BackupTransportClient transport = new BackupTransportClient(transportBinder);
synchronized (mStateLock) {
checkStateIntegrityLocked();
@@ -492,15 +496,15 @@
private void notifyListener(
TransportConnectionListener listener,
- @Nullable IBackupTransport transport,
+ @Nullable BackupTransportClient transport,
String caller) {
- String transportString = (transport != null) ? "IBackupTransport" : "null";
+ String transportString = (transport != null) ? "BackupTransportClient" : "null";
log(Priority.INFO, "Notifying [" + caller + "] transport = " + transportString);
mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
}
@GuardedBy("mStateLock")
- private void notifyListenersAndClearLocked(@Nullable IBackupTransport transport) {
+ private void notifyListenersAndClearLocked(@Nullable BackupTransportClient transport) {
for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
TransportConnectionListener listener = entry.getKey();
String caller = entry.getValue();
@@ -510,7 +514,7 @@
}
@GuardedBy("mStateLock")
- private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+ private void setStateLocked(@State int state, @Nullable BackupTransportClient transport) {
log(Priority.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
onStateTransition(mState, state);
mState = state;
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
index 03d35e4..1776c41 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
@@ -18,7 +18,7 @@
import android.annotation.Nullable;
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.transport.BackupTransportClient;
/**
* Listener to be called by {@link TransportConnection#connectAsync(TransportConnectionListener,
@@ -26,13 +26,14 @@
*/
public interface TransportConnectionListener {
/**
- * Called when {@link TransportConnection} has a transport binder available or that it decided
+ * Called when {@link TransportConnection} has a transport client available or that it decided
* it couldn't obtain one, in which case {@param transport} is null.
*
- * @param transport A {@link IBackupTransport} transport binder or null.
+ * @param transportClient A {@link BackupTransportClient} transport or null.
* @param transportConnection The {@link TransportConnection} used to retrieve this transport
- * binder.
+ * client.
*/
void onTransportConnectionResult(
- @Nullable IBackupTransport transport, TransportConnection transportConnection);
+ @Nullable BackupTransportClient transportClient,
+ TransportConnection transportConnection);
}
diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java
new file mode 100644
index 0000000..466f647
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/OperationStorage.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * OperationStorage is an abstraction around a set of active operations.
+ *
+ * Operations are registered with a token that must first be obtained from
+ * {@link UserBackupManagerService#generateRandomIntegerToken()}. When
+ * registering, the caller may also associate a set of package names with
+ * the operation.
+ *
+ * TODO(b/208442527): have the token be generated within and returned by
+ * registerOperation, as it should be an internal detail.
+ *
+ * Operations have a type and a state. Although ints, the values that can
+ * be used are defined in {@link UserBackupManagerService}. If the type of
+ * an operation is OP_BACKUP, then it represents a task running backups. The
+ * task is provided when registering the operation because it provides a
+ * handle to cancel the backup.
+ */
+public interface OperationStorage {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ OpState.PENDING,
+ OpState.ACKNOWLEDGED,
+ OpState.TIMEOUT
+ })
+ public @interface OpState {
+ // The operation is in progress.
+ int PENDING = 0;
+ // The operation has been acknowledged.
+ int ACKNOWLEDGED = 1;
+ // The operation has timed out.
+ int TIMEOUT = -1;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ OpType.BACKUP_WAIT,
+ OpType.RESTORE_WAIT,
+ OpType.BACKUP,
+ })
+ public @interface OpType {
+ // Waiting for backup agent to respond during backup operation.
+ int BACKUP_WAIT = 0;
+ // Waiting for backup agent to respond during restore operation.
+ int RESTORE_WAIT = 1;
+ // An entire backup operation spanning multiple packages.
+ int BACKUP = 2;
+ }
+
+ /**
+ * Record an ongoing operation of given type and in the given initial
+ * state. The associated task is used as a callback.
+ *
+ * @param token an operation token issued by
+ * {@link UserBackupManagerService#generateRandomIntegerToken()}
+ * @param initialState the state that the operation starts in
+ * @param task the {@link BackupRestoreTask} that is expected to
+ * remove the operation on completion, and which may
+ * be notified if the operation requires cancelling.
+ * @param type the type of the operation.
+ */
+ void registerOperation(int token, @OpState int initialState,
+ BackupRestoreTask task, @OpType int type);
+
+ /**
+ * See {@link #registerOperation()}. In addition this method accepts a set
+ * of package names which are associated with the operation.
+ *
+ * @param token See {@link #registerOperation()}
+ * @param initialState See {@link #registerOperation()}
+ * @param packageNames the package names to associate with the operation.
+ * @param task See {@link #registerOperation()}
+ * @param type See {@link #registerOperation()}
+ */
+ void registerOperationForPackages(int token, @OpState int initialState,
+ Set<String> packageNames, BackupRestoreTask task, @OpType int type);
+
+ /**
+ * Remove the operation identified by token. This is called when the
+ * operation is no longer in progress and should be dropped. Any association
+ * with package names provided in {@link #registerOperation()} is dropped as
+ * well.
+ *
+ * @param token the operation token specified when registering the operation.
+ */
+ void removeOperation(int token);
+
+ /**
+ * Obtain the number of currently registered operations.
+ *
+ * @return the number of currently registered operations.
+ */
+ int numOperations();
+
+ /**
+ * Determine if a backup operation is in progress or not.
+ *
+ * @return true if any operation is registered of type BACKUP and in
+ * state PENDING.
+ */
+ boolean isBackupOperationInProgress();
+
+ /**
+ * Obtain a set of operation tokens for all pending operations that were
+ * registered with an association to the specified package name.
+ *
+ * @param packageName the name of the package used at registration time
+ *
+ * @return a set of operation tokens associated to package name.
+ */
+ Set<Integer> operationTokensForPackage(String packageName);
+
+ /**
+ * Obtain a set of operation tokens for all pending operations that are
+ * of the specified operation type.
+ *
+ * @param type the type of the operation provided at registration time.
+ *
+ * @return a set of operation tokens for operations of that type.
+ */
+ Set<Integer> operationTokensForOpType(@OpType int type);
+
+ /**
+ * Obtain a set of operation tokens for all pending operations that are
+ * currently in the specified operation state.
+ *
+ * @param state the state of the operation.
+ *
+ * @return a set of operation tokens for operations in that state.
+ */
+ Set<Integer> operationTokensForOpState(@OpState int state);
+};
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 452adb2..98ea03e 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -103,7 +103,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
@@ -127,6 +126,7 @@
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.ActiveRestoreSession;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.transport.TransportNotRegisteredException;
@@ -3719,7 +3719,8 @@
mTransportManager.getTransportClient(newTransportName, callerLogString);
if (transportConnection != null) {
try {
- IBackupTransport transport = transportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = transportConnection.connectOrThrow(
+ callerLogString);
mCurrentToken = transport.getCurrentRestoreSet();
} catch (Exception e) {
// Oops. We can't know the current dataset token, so reset and figure it out
@@ -4371,7 +4372,7 @@
final long oldCallingId = Binder.clearCallingIdentity();
try {
- IBackupTransport transport = transportConnection.connectOrThrow(
+ BackupTransportClient transport = transportConnection.connectOrThrow(
/* caller */ "BMS.getOperationTypeFromTransport");
if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) {
return OperationType.MIGRATION;
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 1c86091..9ce4eab 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -41,7 +41,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -51,6 +50,7 @@
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.remote.RemoteCall;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -300,7 +300,7 @@
mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
try {
// If we're running a backup we should be connected to a transport
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.getConnectedTransport("PFTBT.handleCancel()");
transport.cancelFullBackup();
} catch (RemoteException | TransportNotAvailableException e) {
@@ -353,7 +353,7 @@
return;
}
- IBackupTransport transport = mTransportConnection.connect("PFTBT.run()");
+ BackupTransportClient transport = mTransportConnection.connect("PFTBT.run()");
if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -745,7 +745,7 @@
Slog.v(TAG, "Got preflight response; size=" + totalSize);
}
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("PFTBT$SPBP.preflightFullBackup()");
result = transport.checkFullBackupSize(totalSize);
if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 3b3bf8c6..5c24859 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -31,7 +31,6 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -51,6 +50,7 @@
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import java.util.ArrayList;
@@ -149,7 +149,7 @@
String callerLogString = "BH/MSG_RUN_BACKUP";
TransportConnection transportConnection =
transportManager.getCurrentTransportClient(callerLogString);
- IBackupTransport transport =
+ BackupTransportClient transport =
transportConnection != null
? transportConnection.connect(callerLogString)
: null;
@@ -364,7 +364,7 @@
RestoreGetSetsParams params = (RestoreGetSetsParams) msg.obj;
String callerLogString = "BH/MSG_RUN_GET_RESTORE_SETS";
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
params.mTransportConnection.connectOrThrow(callerLogString);
sets = transport.getAvailableRestoreSets();
// cache the result in the active session
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
new file mode 100644
index 0000000..6908c60
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.BackupManagerService.DEBUG;
+import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
+
+import android.annotation.UserIdInt;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage;
+
+import com.google.android.collect.Sets;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+/**
+ * LifecycleOperationStorage is responsible for maintaining a set of currently
+ * active operations. Each operation has a type and state, and a callback that
+ * can receive events upon operation completion or cancellation. It may also
+ * be associated with one or more package names.
+ *
+ * An operation wraps a {@link BackupRestoreTask} within it.
+ * It's the responsibility of this task to remove the operation from this array.
+ *
+ * If type of operation is {@code OP_TYPE_WAIT}, it is waiting for an ACK or
+ * timeout.
+ *
+ * A BackupRestore task gets notified of AVK/timeout for the operation via
+ * {@link BackupRestoreTask#handleCancel()},
+ * {@link BackupRestoreTask#operationComplete()} and {@code notifyAll} called
+ * on the {@code mCurrentOpLock}.
+ *
+ * {@link LifecycleOperationStorage#waitUntilOperationComplete(int)} is used in
+ * various places to 'wait' for notifyAll and detect change of pending state of
+ * an operation. So typically, an operation will be removed from this array by:
+ * - {@link BackupRestoreTask#handleCancel()} and
+ * - {@link BackupRestoreTask#operationComplete()} OR
+ * {@link BackupRestoreTask#waitUntilOperationComplete()}.
+ * Do not remove at both these places because {@code waitUntilOperationComplete}
+ * relies on the operation being present to determine its completion status.
+ *
+ * If type of operation is {@code OP_BACKUP}, it is a task running backups. It
+ * provides a handle to cancel backup tasks.
+ */
+public class LifecycleOperationStorage implements OperationStorage {
+ private static final String TAG = "LifecycleOperationStorage";
+
+ private final int mUserId;
+
+ private final Object mOperationsLock = new Object();
+
+ // Bookkeeping of in-flight operations. The operation token is the index of
+ // the entry in the pending operations list.
+ @GuardedBy("mOperationsLock")
+ private final SparseArray<Operation> mOperations = new SparseArray<>();
+
+ // Association from package name to one or more operations relating to that
+ // package.
+ @GuardedBy("mOperationsLock")
+ private final Map<String, Set<Integer>> mOpTokensByPackage = new HashMap<>();
+
+ public LifecycleOperationStorage(@UserIdInt int userId) {
+ this.mUserId = userId;
+ }
+
+ /** See {@link OperationStorage#registerOperation()} */
+ @Override
+ public void registerOperation(int token, @OpState int initialState,
+ BackupRestoreTask task, @OpType int type) {
+ registerOperationForPackages(token, initialState, Sets.newHashSet(), task, type);
+ }
+
+ /** See {@link OperationStorage#registerOperationForPackages()} */
+ @Override
+ public void registerOperationForPackages(int token, @OpState int initialState,
+ Set<String> packageNames, BackupRestoreTask task, @OpType int type) {
+ synchronized (mOperationsLock) {
+ mOperations.put(token, new Operation(initialState, task, type));
+ for (String packageName : packageNames) {
+ Set<Integer> tokens = mOpTokensByPackage.get(packageName);
+ if (tokens == null) {
+ tokens = new HashSet<Integer>();
+ }
+ tokens.add(token);
+ mOpTokensByPackage.put(packageName, tokens);
+ }
+ }
+ }
+
+ /** See {@link OperationStorage#removeOperation()} */
+ @Override
+ public void removeOperation(int token) {
+ synchronized (mOperationsLock) {
+ mOperations.remove(token);
+ Set<String> packagesWithTokens = mOpTokensByPackage.keySet();
+ for (String packageName : packagesWithTokens) {
+ Set<Integer> tokens = mOpTokensByPackage.get(packageName);
+ if (tokens == null) {
+ continue;
+ }
+ tokens.remove(token);
+ mOpTokensByPackage.put(packageName, tokens);
+ }
+ }
+ }
+
+ /** See {@link OperationStorage#numOperations()}. */
+ @Override
+ public int numOperations() {
+ synchronized (mOperationsLock) {
+ return mOperations.size();
+ }
+ }
+
+ /** See {@link OperationStorage#isBackupOperationInProgress()}. */
+ @Override
+ public boolean isBackupOperationInProgress() {
+ synchronized (mOperationsLock) {
+ for (int i = 0; i < mOperations.size(); i++) {
+ Operation op = mOperations.valueAt(i);
+ if (op.type == OpType.BACKUP && op.state == OpState.PENDING) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /** See {@link OperationStorage#operationTokensForPackage()} */
+ @Override
+ public Set<Integer> operationTokensForPackage(String packageName) {
+ synchronized (mOperationsLock) {
+ final Set<Integer> tokens = mOpTokensByPackage.get(packageName);
+ Set<Integer> result = Sets.newHashSet();
+ if (tokens != null) {
+ result.addAll(tokens);
+ }
+ return result;
+ }
+ }
+
+ /** See {@link OperationStorage#operationTokensForOpType()} */
+ @Override
+ public Set<Integer> operationTokensForOpType(@OpType int type) {
+ Set<Integer> tokens = Sets.newHashSet();
+ synchronized (mOperationsLock) {
+ for (int i = 0; i < mOperations.size(); i++) {
+ final Operation op = mOperations.valueAt(i);
+ final int token = mOperations.keyAt(i);
+ if (op.type == type) {
+ tokens.add(token);
+ }
+ }
+ return tokens;
+ }
+ }
+
+ /** See {@link OperationStorage#operationTokensForOpState()} */
+ @Override
+ public Set<Integer> operationTokensForOpState(@OpState int state) {
+ Set<Integer> tokens = Sets.newHashSet();
+ synchronized (mOperationsLock) {
+ for (int i = 0; i < mOperations.size(); i++) {
+ final Operation op = mOperations.valueAt(i);
+ final int token = mOperations.keyAt(i);
+ if (op.state == state) {
+ tokens.add(token);
+ }
+ }
+ return tokens;
+ }
+ }
+
+ /**
+ * A blocking function that blocks the caller until the operation identified
+ * by {@code token} is complete - either via a message from the backup,
+ * agent or through cancellation.
+ *
+ * @param token the operation token specified when registering the operation
+ * @param callback a lambda which is invoked once only when the operation
+ * completes - ie. if this method is called twice for the
+ * same token, the lambda is not invoked the second time.
+ * @return true if the operation was ACKed prior to or during this call.
+ */
+ public boolean waitUntilOperationComplete(int token, IntConsumer callback) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "[UserID:" + mUserId + "] Blocking until operation complete for "
+ + Integer.toHexString(token));
+ }
+ @OpState int finalState = OpState.PENDING;
+ Operation op = null;
+ synchronized (mOperationsLock) {
+ while (true) {
+ op = mOperations.get(token);
+ if (op == null) {
+ // mysterious disappearance: treat as success with no callback
+ break;
+ } else {
+ if (op.state == OpState.PENDING) {
+ try {
+ mOperationsLock.wait();
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Waiting on mOperationsLock: ", e);
+ }
+ // When the wait is notified we loop around and recheck the current state
+ } else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "[UserID:" + mUserId
+ + "] Unblocked waiting for operation token="
+ + Integer.toHexString(token));
+ }
+ // No longer pending; we're done
+ finalState = op.state;
+ break;
+ }
+ }
+ }
+ }
+
+ removeOperation(token);
+ if (op != null) {
+ callback.accept(op.type);
+ }
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "[UserID:" + mUserId + "] operation " + Integer.toHexString(token)
+ + " complete: finalState=" + finalState);
+ }
+ return finalState == OpState.ACKNOWLEDGED;
+ }
+
+ /**
+ * Signals that an ongoing operation is complete: after a currently-active
+ * backup agent has notified us that it has completed the outstanding
+ * asynchronous backup/restore operation identified by the supplied
+ * {@code} token.
+ *
+ * @param token the operation token specified when registering the operation
+ * @param result a result code or error code for the completed operation
+ * @param callback a lambda that is invoked if the completion moves the
+ * operation from PENDING to ACKNOWLEDGED state.
+ */
+ public void onOperationComplete(int token, long result, Consumer<BackupRestoreTask> callback) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "[UserID:" + mUserId + "] onOperationComplete: "
+ + Integer.toHexString(token) + " result=" + result);
+ }
+ Operation op = null;
+ synchronized (mOperationsLock) {
+ op = mOperations.get(token);
+ if (op != null) {
+ if (op.state == OpState.TIMEOUT) {
+ // The operation already timed out, and this is a late response. Tidy up
+ // and ignore it; we've already dealt with the timeout.
+ op = null;
+ mOperations.remove(token);
+ } else if (op.state == OpState.ACKNOWLEDGED) {
+ if (DEBUG) {
+ Slog.w(TAG, "[UserID:" + mUserId + "] Received duplicate ack for token="
+ + Integer.toHexString(token));
+ }
+ op = null;
+ mOperations.remove(token);
+ } else if (op.state == OpState.PENDING) {
+ // Can't delete op from mOperations. waitUntilOperationComplete can be
+ // called after we we receive this call.
+ op.state = OpState.ACKNOWLEDGED;
+ }
+ }
+ mOperationsLock.notifyAll();
+ }
+
+ // Invoke the operation's completion callback, if there is one.
+ if (op != null && op.callback != null) {
+ callback.accept(op.callback);
+ }
+ }
+
+ /**
+ * Cancel the operation associated with {@code token}. Cancellation may be
+ * propagated to the operation's callback (a {@link BackupRestoreTask}) if
+ * the operation has one, and the cancellation is due to the operation
+ * timing out.
+ *
+ * @param token the operation token specified when registering the operation
+ * @param cancelAll this is passed on when propagating the cancellation
+ * @param operationTimedOutCallback a lambda that is invoked with the
+ * operation type where the operation is
+ * cancelled due to timeout, allowing the
+ * caller to do type-specific clean-ups.
+ */
+ public void cancelOperation(
+ int token, boolean cancelAll, IntConsumer operationTimedOutCallback) {
+ // Notify any synchronous waiters
+ Operation op = null;
+ synchronized (mOperationsLock) {
+ op = mOperations.get(token);
+ if (MORE_DEBUG) {
+ if (op == null) {
+ Slog.w(TAG, "[UserID:" + mUserId + "] Cancel of token "
+ + Integer.toHexString(token) + " but no op found");
+ }
+ }
+ int state = (op != null) ? op.state : OpState.TIMEOUT;
+ if (state == OpState.ACKNOWLEDGED) {
+ // The operation finished cleanly, so we have nothing more to do.
+ if (DEBUG) {
+ Slog.w(TAG, "[UserID:" + mUserId + "] Operation already got an ack."
+ + "Should have been removed from mCurrentOperations.");
+ }
+ op = null;
+ mOperations.delete(token);
+ } else if (state == OpState.PENDING) {
+ if (DEBUG) {
+ Slog.v(TAG, "[UserID:" + mUserId + "] Cancel: token="
+ + Integer.toHexString(token));
+ }
+ op.state = OpState.TIMEOUT;
+ // Can't delete op from mOperations here. waitUntilOperationComplete may be
+ // called after we receive cancel here. We need this op's state there.
+ operationTimedOutCallback.accept(op.type);
+ }
+ mOperationsLock.notifyAll();
+ }
+
+ // If there's a TimeoutHandler for this event, call it
+ if (op != null && op.callback != null) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "[UserID:" + mUserId + " Invoking cancel on " + op.callback);
+ }
+ op.callback.handleCancel(cancelAll);
+ }
+ }
+};
diff --git a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
index 80bd604..de0177c 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
@@ -21,9 +21,9 @@
import android.content.pm.PackageInfo;
import android.util.Slog;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import java.io.File;
@@ -47,7 +47,7 @@
public void run() {
String callerLogString = "PerformClearTask.run()";
- IBackupTransport transport = null;
+ BackupTransportClient transport = null;
try {
// Clear the on-device backup state to ensure a full backup next time
String transportDirName =
diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
index 7636ef6..888f49d 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -28,10 +28,10 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import java.io.File;
@@ -128,7 +128,8 @@
EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
long startRealtime = SystemClock.elapsedRealtime();
- IBackupTransport transport = transportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = transportConnection.connectOrThrow(
+ callerLogString);
int status = transport.initializeDevice();
if (status != BackupTransport.TRANSPORT_OK) {
Slog.e(TAG, "Transport error in initializeDevice()");
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index bdb2e6f..30da8c1 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -51,7 +51,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupAgentTimeoutParameters;
@@ -65,6 +64,7 @@
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.remote.RemoteCallable;
import com.android.server.backup.remote.RemoteResult;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -111,7 +111,7 @@
* </ul>
*
* If there is no PackageManager (PM) pseudo-package state file in the state directory, the
- * specified transport will be initialized with {@link IBackupTransport#initializeDevice()}.
+ * specified transport will be initialized with {@link BackupTransportClient#initializeDevice()}.
*
* <p>The PM pseudo-package is the first package to be backed-up and sent to the transport in case
* of incremental choice. If non-incremental, PM will only be backed-up if specified in the queue,
@@ -141,8 +141,8 @@
* </ul>
* <li>Unbind the agent.
* <li>Assuming agent response, send the staged data that the agent wrote to disk to the transport
- * via {@link IBackupTransport#performBackup(PackageInfo, ParcelFileDescriptor, int)}.
- * <li>Call {@link IBackupTransport#finishBackup()} if previous call was successful.
+ * via {@link BackupTransportClient#performBackup(PackageInfo, ParcelFileDescriptor, int)}.
+ * <li>Call {@link BackupTransportClient#finishBackup()} if previous call was successful.
* <li>Save the new state in the state file. During the agent call it was being written to
* <state file>.new, here we rename it and replace the old one.
* <li>Delete the stage file.
@@ -155,7 +155,7 @@
* <li>Delete the {@link DataChangedJournal} provided. Note that this should not be the current
* journal.
* <li>Set {@link UserBackupManagerService} current token as {@link
- * IBackupTransport#getCurrentRestoreSet()}, if applicable.
+ * BackupTransportClient#getCurrentRestoreSet()}, if applicable.
* <li>Add the transport to the list of transports pending initialization ({@link
* UserBackupManagerService#getPendingInits()}) and kick-off initialization if the transport
* ever returned {@link BackupTransport#TRANSPORT_NOT_INITIALIZED}.
@@ -194,7 +194,7 @@
* @param backupManagerService The {@link UserBackupManagerService} instance.
* @param transportConnection The {@link TransportConnection} that contains the transport used
* for the operation.
- * @param transportDirName The value of {@link IBackupTransport#transportDirName()} for the
+ * @param transportDirName The value of {@link BackupTransportClient#transportDirName()} for the
* transport whose {@link TransportConnection} was provided above.
* @param queue The list of package names that will be backed-up.
* @param dataChangedJournal The old data-changed journal file that will be deleted when the
@@ -417,7 +417,7 @@
boolean noDataPackageEncountered = false;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.informTransportOfEmptyBackups()");
for (String packageName : succeedingPackages) {
@@ -467,8 +467,8 @@
}
/** Send the "no data changed" message to a transport for a specific package */
- private void sendNoDataChangedTo(IBackupTransport transport, PackageInfo packageInfo, int flags)
- throws RemoteException {
+ private void sendNoDataChangedTo(BackupTransportClient transport, PackageInfo packageInfo,
+ int flags) throws RemoteException {
ParcelFileDescriptor pfd;
try {
pfd = ParcelFileDescriptor.open(mBlankStateFile, MODE_READ_ONLY | MODE_CREATE);
@@ -608,7 +608,8 @@
mReporter.onQueueReady(mQueue);
File pmState = new File(mStateDirectory, PM_PACKAGE);
try {
- IBackupTransport transport = mTransportConnection.connectOrThrow("KVBT.startTask()");
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
+ "KVBT.startTask()");
String transportName = transport.name();
if (transportName.contains("EncryptedLocalTransport")) {
// Temporary code for EiTF POC. Only supports non-incremental backups.
@@ -764,7 +765,8 @@
long currentToken = mBackupManagerService.getCurrentToken();
if (mHasDataToBackup && (status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
try {
- IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
+ callerLogString);
transportName = transport.name();
mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
mBackupManagerService.writeRestoreTokens();
@@ -835,7 +837,7 @@
@GuardedBy("mQueueLock")
private void triggerTransportInitializationLocked() throws Exception {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.triggerTransportInitializationLocked");
mBackupManagerService.getPendingInits().add(transport.name());
deletePmStateFile();
@@ -919,7 +921,7 @@
}
}
- IBackupTransport transport = mTransportConnection.connectOrThrow(
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
"KVBT.extractAgentData()");
long quota = transport.getBackupQuota(packageName, /* isFullBackup */ false);
int transportFlags = transport.getTransportFlags();
@@ -1078,7 +1080,7 @@
int status;
try (ParcelFileDescriptor backupData =
ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.transportPerformBackup()");
mReporter.onTransportPerformBackup(packageName);
int flags = getPerformBackupFlags(mUserInitiated, nonIncremental);
@@ -1131,7 +1133,7 @@
private void agentDoQuotaExceeded(@Nullable IBackupAgent agent, String packageName, long size) {
if (agent != null) {
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.agentDoQuotaExceeded()");
long quota = transport.getBackupQuota(packageName, false);
remoteCall(
@@ -1227,7 +1229,7 @@
mReporter.onRevertTask();
long delay;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.revertTask()");
delay = transport.requestBackupTime();
} catch (Exception e) {
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 8c786d5..ac831af 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -54,7 +54,6 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -66,6 +65,7 @@
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
@@ -397,7 +397,7 @@
PackageInfo[] packages = mAcceptSet.toArray(new PackageInfo[0]);
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("PerformUnifiedRestoreTask.startRestore()");
mStatus = transport.startRestore(mToken, packages);
@@ -495,7 +495,7 @@
private void dispatchNextRestore() {
UnifiedRestoreState nextState = UnifiedRestoreState.FINAL;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(
"PerformUnifiedRestoreTask.dispatchNextRestore()");
mRestoreDescription = transport.nextRestorePackage();
@@ -709,7 +709,7 @@
boolean startedAgentRestore = false;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(
"PerformUnifiedRestoreTask.initiateOneRestore()");
@@ -940,7 +940,8 @@
String callerLogString = "PerformUnifiedRestoreTask$StreamFeederThread.run()";
try {
- IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
+ callerLogString);
while (status == BackupTransport.TRANSPORT_OK) {
// have the transport write some of the restoring data to us
int result = transport.getNextFullRestoreDataChunk(tWriteEnd);
@@ -1032,7 +1033,7 @@
// Something went wrong somewhere. Whether it was at the transport
// level is immaterial; we need to tell the transport to bail
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(callerLogString);
transport.abortFullRestore();
} catch (Exception e) {
@@ -1095,7 +1096,7 @@
String callerLogString = "PerformUnifiedRestoreTask.finalizeRestore()";
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(callerLogString);
transport.finishRestore();
} catch (Exception e) {
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index 652386f..bd1ac2dc 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -40,8 +40,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.ArrayUtils;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.google.android.collect.Sets;
@@ -237,7 +237,7 @@
}
if (transportConnection != null) {
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
transportConnection.connectOrThrow(
"AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport");
return transport.isAppEligibleForBackup(
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
new file mode 100644
index 0000000..ee09832
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+/**
+ * Virtual device manager local service interface.
+ */
+public abstract class VirtualDeviceManagerInternal {
+
+ /**
+ * Returns true if the given {@code uid} is the owner of any virtual devices that are
+ * currently active.
+ */
+ public abstract boolean isAppOwnerOfAnyVirtualDevice(int uid);
+
+ /**
+ * Returns true if the given {@code uid} is currently running on any virtual devices. This is
+ * determined by whether the app has any activities in the task stack on a virtual-device-owned
+ * display.
+ */
+ public abstract boolean isAppRunningOnAnyVirtualDevice(int uid);
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 58d0801..8592c05 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -81,6 +81,7 @@
@Override
public void onStart() {
publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
+ publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
}
@Override
@@ -119,8 +120,10 @@
private class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient {
private final AssociationInfo mAssociationInfo;
+ private final int mOwnerUid;
- private VirtualDeviceImpl(IBinder token, AssociationInfo associationInfo) {
+ private VirtualDeviceImpl(int ownerUid, IBinder token, AssociationInfo associationInfo) {
+ mOwnerUid = ownerUid;
mAssociationInfo = associationInfo;
try {
token.linkToDeath(this, 0);
@@ -156,10 +159,11 @@
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"createVirtualDevice");
- if (!PermissionUtils.validatePackageName(getContext(), packageName, getCallingUid())) {
+ final int callingUid = getCallingUid();
+ if (!PermissionUtils.validatePackageName(getContext(), packageName, callingUid)) {
throw new SecurityException(
"Package name " + packageName + " does not belong to calling uid "
- + getCallingUid());
+ + callingUid);
}
AssociationInfo associationInfo = getAssociationInfo(packageName, associationId);
if (associationInfo == null) {
@@ -171,7 +175,7 @@
"Virtual device for association ID " + associationId
+ " already exists");
}
- return new VirtualDeviceImpl(token, associationInfo);
+ return new VirtualDeviceImpl(callingUid, token, associationInfo);
}
}
@@ -222,4 +226,26 @@
}
}
}
+
+ private final class LocalService extends VirtualDeviceManagerInternal {
+
+ @Override
+ public boolean isAppOwnerOfAnyVirtualDevice(int uid) {
+ synchronized (mVirtualDeviceManagerLock) {
+ int size = mVirtualDevices.size();
+ for (int i = 0; i < size; i++) {
+ if (mVirtualDevices.valueAt(i).mOwnerUid == uid) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isAppRunningOnAnyVirtualDevice(int uid) {
+ // TODO(yukl): Implement this using DWPC.onRunningAppsChanged
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index b641377..71b463a 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -25,13 +25,14 @@
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
-per-file IpSecService.java = file:/services/core/java/com/android/server/net/OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b7b4870..9180ef8 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -45,7 +45,6 @@
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.power.MeasuredEnergyStats;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import libcore.util.EmptyArray;
@@ -260,43 +259,6 @@
}
@Override
- public Future<?> scheduleReadProcStateCpuTimes(
- boolean onBattery, boolean onBatteryScreenOff, long delayMillis) {
- synchronized (mStats) {
- if (!mStats.trackPerProcStateCpuTimes()) {
- return null;
- }
- }
- synchronized (BatteryExternalStatsWorker.this) {
- if (!mExecutorService.isShutdown()) {
- return mExecutorService.schedule(PooledLambda.obtainRunnable(
- BatteryStatsImpl::updateProcStateCpuTimes,
- mStats, onBattery, onBatteryScreenOff).recycleOnUse(),
- delayMillis, TimeUnit.MILLISECONDS);
- }
- }
- return null;
- }
-
- @Override
- public Future<?> scheduleCopyFromAllUidsCpuTimes(
- boolean onBattery, boolean onBatteryScreenOff) {
- synchronized (mStats) {
- if (!mStats.trackPerProcStateCpuTimes()) {
- return null;
- }
- }
- synchronized (BatteryExternalStatsWorker.this) {
- if (!mExecutorService.isShutdown()) {
- return mExecutorService.submit(PooledLambda.obtainRunnable(
- BatteryStatsImpl::copyFromAllUidsCpuTimes,
- mStats, onBattery, onBatteryScreenOff).recycleOnUse());
- }
- }
- return null;
- }
-
- @Override
public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
synchronized (BatteryExternalStatsWorker.this) {
@@ -491,7 +453,7 @@
}
if ((updateFlags & UPDATE_CPU) != 0) {
- mStats.copyFromAllUidsCpuTimes();
+ mStats.updateCpuTimesForAllUids();
}
// Clean up any UIDs if necessary.
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ff451a3..e4ac7be 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -493,6 +493,12 @@
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
}
+ /*package*/ boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
+ synchronized (mDeviceStateLock) {
+ return mDeviceInventory.isDeviceConnected(device);
+ }
+ }
+
/*package*/ void setWiredDeviceConnectionState(int type,
@AudioService.ConnectionState int state, String address, String name,
String caller) {
@@ -502,6 +508,13 @@
}
}
+ /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ @AudioService.ConnectionState int state) {
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.setTestDeviceConnectionState(device, state);
+ }
+ }
+
/*package*/ static final class BleVolumeInfo {
final int mIndex;
final int mMaxIndex;
@@ -1002,7 +1015,8 @@
/*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
String deviceName) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+ return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName,
+ false /*for test*/);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index f32d3b5..a27e4b77 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -224,6 +224,7 @@
public final String mAddress;
public final String mName;
public final String mCaller;
+ public boolean mForTest = false;
/*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
String address, String name, String caller) {
@@ -521,7 +522,7 @@
}
if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
- wdcs.mType, wdcs.mAddress, wdcs.mName)) {
+ wdcs.mType, wdcs.mAddress, wdcs.mName, wdcs.mForTest)) {
// change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record();
@@ -593,7 +594,7 @@
}
//------------------------------------------------------------
- //
+ // preferred device(s)
/*package*/ int setPreferredDevicesForStrategySync(int strategy,
@NonNull List<AudioDeviceAttributes> devices) {
@@ -674,16 +675,34 @@
mDevRoleCapturePresetDispatchers.unregister(dispatcher);
}
+ //-----------------------------------------------------------------------
+
+ /**
+ * Check if a device is in the list of connected devices
+ * @param device the device whose connection state is queried
+ * @return true if connected
+ */
+ // called with AudioDeviceBroker.mDeviceStateLock lock held
+ public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
+ final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
+ device.getAddress());
+ synchronized (mDevicesLock) {
+ return (mConnectedDevices.get(key) != null);
+ }
+ }
+
/**
* Implements the communication with AudioSystem to (dis)connect a device in the native layers
* @param connect true if connection
* @param device the device type
* @param address the address of the device
* @param deviceName human-readable name of device
+ * @param isForTesting if true, not calling AudioSystem for the connection as this is
+ * just for testing
* @return false if an error was reported by AudioSystem
*/
/*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
- String deviceName) {
+ String deviceName, boolean isForTesting) {
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ Integer.toHexString(device) + " address:" + address
@@ -706,9 +725,14 @@
Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
}
if (connect && !isConnected) {
- final int res = mAudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ final int res;
+ if (isForTesting) {
+ res = AudioSystem.AUDIO_STATUS_OK;
+ } else {
+ res = mAudioSystem.setDeviceConnectionState(device,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
if (res != AudioSystem.AUDIO_STATUS_OK) {
final String reason = "not connecting device 0x" + Integer.toHexString(device)
+ " due to command error " + res;
@@ -914,6 +938,15 @@
}
}
+ /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ @AudioService.ConnectionState int state) {
+ final WiredDeviceConnectionState connection = new WiredDeviceConnectionState(
+ device.getInternalType(), state, device.getAddress(),
+ "test device", "com.android.server.audio");
+ connection.mForTest = true;
+ onSetWiredDeviceConnectionState(connection);
+ }
+
//-------------------------------------------------------------------
// Internal utilities
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9c8a663..aa33644 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -35,6 +35,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -92,6 +93,7 @@
import android.media.IAudioService;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
+import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -1027,7 +1029,8 @@
readUserRestrictions();
mPlaybackMonitor =
- new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+ new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
+ device -> onMuteAwaitConnectionTimeout(device));
mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1048,6 +1051,9 @@
mHasSpatializerEffect = SystemProperties.getBoolean("ro.audio.spatializer_enabled", false);
+ // monitor routing updates coming from native
+ mAudioSystem.setRoutingListener(this);
+
// done with service initialization, continue additional work in our Handler thread
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
@@ -1251,20 +1257,22 @@
// routing monitoring from AudioSystemAdapter
@Override
public void onRoutingUpdatedFromNative() {
- if (!mHasSpatializerEffect) {
- return;
- }
sendMsg(mAudioHandler,
MSG_ROUTING_UPDATED,
SENDMSG_REPLACE, 0, 0, null,
/*delay*/ 0);
}
- void monitorRoutingChanges(boolean enabled) {
- mAudioSystem.setRoutingListener(enabled ? this : null);
+ /**
+ * called when handling MSG_ROUTING_UPDATED
+ */
+ void onRoutingUpdatedFromAudioThread() {
+ if (mHasSpatializerEffect) {
+ mSpatializerHelper.onRoutingUpdated();
+ }
+ checkMuteAwaitConnection();
}
-
//-----------------------------------------------------------------
RoleObserver mRoleObserver;
@@ -1452,7 +1460,6 @@
if (mHasSpatializerEffect) {
mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled());
- monitorRoutingChanges(true);
}
onIndicateSystemReady();
@@ -6357,6 +6364,20 @@
mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
}
+ /** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
+ public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ boolean connected) {
+ Objects.requireNonNull(device);
+ enforceModifyAudioRoutingPermission();
+ mDeviceBroker.setTestDeviceConnectionState(device,
+ connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
+ // simulate a routing update from native
+ sendMsg(mAudioHandler,
+ MSG_ROUTING_UPDATED,
+ SENDMSG_REPLACE, 0, 0, null,
+ /*delay*/ 0);
+ }
+
/**
* @hide
* The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
@@ -7652,7 +7673,6 @@
mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
if (mHasSpatializerEffect) {
mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled());
- monitorRoutingChanges(true);
}
mAudioEventWakeLock.release();
break;
@@ -7791,7 +7811,7 @@
break;
case MSG_ROUTING_UPDATED:
- mSpatializerHelper.onRoutingUpdated();
+ onRoutingUpdatedFromAudioThread();
break;
case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
@@ -8612,6 +8632,171 @@
}
//==========================================================================================
+ private final Object mMuteAwaitConnectionLock = new Object();
+
+ /**
+ * The device that is expected to be connected soon, and causes players to be muted until
+ * its connection, or it times out.
+ * Null when no active muting command, or it has timed out.
+ */
+ @GuardedBy("mMuteAwaitConnectionLock")
+ private AudioDeviceAttributes mMutingExpectedDevice;
+ @GuardedBy("mMuteAwaitConnectionLock")
+ private @Nullable int[] mMutedUsagesAwaitingConnection;
+
+ /** @see AudioManager#muteAwaitConnection */
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ public void muteAwaitConnection(@NonNull int[] usages,
+ @NonNull AudioDeviceAttributes device, long timeOutMs) {
+ Objects.requireNonNull(usages);
+ Objects.requireNonNull(device);
+ enforceModifyAudioRoutingPermission();
+ if (timeOutMs <= 0 || usages.length == 0) {
+ throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
+ }
+
+ if (mDeviceBroker.isDeviceConnected(device)) {
+ // not throwing an exception as there could be a race between a connection (server-side,
+ // notification of connection in flight) and a mute operation (client-side)
+ Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
+ return;
+ }
+ synchronized (mMuteAwaitConnectionLock) {
+ if (mMutingExpectedDevice != null) {
+ Log.e(TAG, "muteAwaitConnection ignored, another in progress for device:"
+ + mMutingExpectedDevice);
+ throw new IllegalStateException("muteAwaitConnection already in progress");
+ }
+ mMutingExpectedDevice = device;
+ mMutedUsagesAwaitingConnection = usages;
+ mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
+ }
+ dispatchMuteAwaitConnection(cb -> { try {
+ cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
+ }
+
+ /** @see AudioManager#getMutingExpectedDevice */
+ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+ enforceModifyAudioRoutingPermission();
+ synchronized (mMuteAwaitConnectionLock) {
+ return mMutingExpectedDevice;
+ }
+ }
+
+ /** @see AudioManager#cancelMuteAwaitConnection */
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
+ Objects.requireNonNull(device);
+ enforceModifyAudioRoutingPermission();
+ Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
+ final int[] mutedUsages;
+ synchronized (mMuteAwaitConnectionLock) {
+ if (mMutingExpectedDevice == null) {
+ // not throwing an exception as there could be a race between a timeout
+ // (server-side) and a cancel operation (client-side)
+ Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
+ return;
+ }
+ if (!device.equals(mMutingExpectedDevice)) {
+ Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
+ + "] but expected device is" + mMutingExpectedDevice);
+ throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
+ }
+ mutedUsages = mMutedUsagesAwaitingConnection;
+ mMutingExpectedDevice = null;
+ mMutedUsagesAwaitingConnection = null;
+ mPlaybackMonitor.cancelMuteAwaitConnection();
+ }
+ dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
+ AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
+ } catch (RemoteException e) { } });
+ }
+
+ final RemoteCallbackList<IMuteAwaitConnectionCallback> mMuteAwaitConnectionDispatchers =
+ new RemoteCallbackList<IMuteAwaitConnectionCallback>();
+
+ /** @see AudioManager#registerMuteAwaitConnectionCallback */
+ public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb,
+ boolean register) {
+ enforceModifyAudioRoutingPermission();
+ if (register) {
+ mMuteAwaitConnectionDispatchers.register(cb);
+ } else {
+ mMuteAwaitConnectionDispatchers.unregister(cb);
+ }
+ }
+
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ void checkMuteAwaitConnection() {
+ final AudioDeviceAttributes device;
+ final int[] mutedUsages;
+ synchronized (mMuteAwaitConnectionLock) {
+ if (mMutingExpectedDevice == null) {
+ return;
+ }
+ device = mMutingExpectedDevice;
+ mutedUsages = mMutedUsagesAwaitingConnection;
+ if (!mDeviceBroker.isDeviceConnected(device)) {
+ return;
+ }
+ mMutingExpectedDevice = null;
+ mMutedUsagesAwaitingConnection = null;
+ Log.i(TAG, "muteAwaitConnection device " + device + " connected, unmuting");
+ mPlaybackMonitor.cancelMuteAwaitConnection();
+ }
+ dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
+ AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
+ } catch (RemoteException e) { } });
+ }
+
+ /**
+ * Called by PlaybackActivityMonitor when the timeout hit for the mute on device connection
+ */
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ void onMuteAwaitConnectionTimeout(@NonNull AudioDeviceAttributes timedOutDevice) {
+ final int[] mutedUsages;
+ synchronized (mMuteAwaitConnectionLock) {
+ if (!timedOutDevice.equals(mMutingExpectedDevice)) {
+ return;
+ }
+ Log.i(TAG, "muteAwaitConnection timeout, clearing expected device "
+ + mMutingExpectedDevice);
+ mutedUsages = mMutedUsagesAwaitingConnection;
+ mMutingExpectedDevice = null;
+ mMutedUsagesAwaitingConnection = null;
+ }
+ dispatchMuteAwaitConnection(cb -> { try {
+ cb.dispatchOnUnmutedEvent(
+ AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
+ timedOutDevice, mutedUsages);
+ } catch (RemoteException e) { } });
+ }
+
+ private void dispatchMuteAwaitConnection(
+ java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
+ final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
+ // lazy initialization as errors unlikely
+ ArrayList<IMuteAwaitConnectionCallback> errorList = null;
+ for (int i = 0; i < nbDispatchers; i++) {
+ try {
+ callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+ } catch (Exception e) {
+ if (errorList == null) {
+ errorList = new ArrayList<>(1);
+ }
+ errorList.add(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+ }
+ }
+ if (errorList != null) {
+ for (IMuteAwaitConnectionCallback errorItem : errorList) {
+ mMuteAwaitConnectionDispatchers.unregister(errorItem);
+ }
+ }
+ mMuteAwaitConnectionDispatchers.finishBroadcast();
+ }
+
+
+ //==========================================================================================
// Device orientation
//==========================================================================================
/**
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index b94cea4..b333ed2 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,9 +17,11 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
@@ -27,21 +29,27 @@
import android.media.PlayerBase;
import android.media.VolumeShaper;
import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Class to receive and dispatch updates from AudioSystem about recording configurations.
@@ -54,6 +62,7 @@
/*package*/ static final boolean DEBUG = false;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
+ /*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
private static final VolumeShaper.Configuration DUCK_VSHAPE =
new VolumeShaper.Configuration.Builder()
@@ -73,6 +82,18 @@
.createIfNeeded()
.build();
+ private static final long UNMUTE_DURATION_MS = 100;
+ private static final VolumeShaper.Configuration MUTE_AWAIT_CONNECTION_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID)
+ .setCurve(new float[] { 0.f, 1.f } /* times */,
+ new float[] { 1.f, 0.f } /* volumes */)
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ // even though we specify a duration, it's only used for the unmute,
+ // for muting this volume shaper is run with PLAY_SKIP_RAMP
+ .setDuration(UNMUTE_DURATION_MS)
+ .build();
+
// TODO support VolumeShaper on those players
private static final int[] UNDUCKABLE_PLAYER_TYPES = {
AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
@@ -90,6 +111,7 @@
private boolean mHasPublicClients = false;
private final Object mPlayerLock = new Object();
+ @GuardedBy("mPlayerLock")
private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
new HashMap<Integer, AudioPlaybackConfiguration>();
@@ -97,12 +119,16 @@
private int mSavedAlarmVolume = -1;
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
+ private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
- PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+ PlaybackActivityMonitor(Context context, int maxAlarmVolume,
+ Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
mContext = context;
mMaxAlarmVolume = maxAlarmVolume;
PlayMonitorClient.sListenerDeathMonitor = this;
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
+ mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
+ initEventHandler();
}
//=================================================================
@@ -170,6 +196,7 @@
sEventLogger.log(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
+ maybeMutePlayerAwaitingConnection(apc);
}
return newPiid;
}
@@ -323,6 +350,7 @@
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
mFadingManager.removeReleased(apc);
+ mMutedPlayersAwaitingConnection.remove(Integer.valueOf(piid));
checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
@@ -451,7 +479,7 @@
pw.println("\n faded out players piids:");
mFadingManager.dump(pw);
// players muted due to the device ringing or being in a call
- pw.print("\n muted player piids:");
+ pw.print("\n muted player piids due to call/ring:");
for (int piid : mMutedPlayers) {
pw.print(" " + piid);
}
@@ -462,6 +490,12 @@
pw.print(" " + uid);
}
pw.println("\n");
+ // muted players:
+ pw.print("\n muted players (piids) awaiting device connection: BL3 ####");
+ for (int piid : mMutedPlayersAwaitingConnection) {
+ pw.print(" " + piid);
+ }
+ pw.println("\n");
// log
sEventLogger.dump(pw);
}
@@ -1100,6 +1134,155 @@
}
}
+ private static final class MuteAwaitConnectionEvent extends AudioEventLogger.Event {
+ private final @NonNull int[] mUsagesToMute;
+
+ MuteAwaitConnectionEvent(@NonNull int[] usagesToMute) {
+ mUsagesToMute = usagesToMute;
+ }
+
+ @Override
+ public String eventToString() {
+ return "muteAwaitConnection muting usages " + Arrays.toString(mUsagesToMute);
+ }
+ }
+
static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
"playback activity as reported through PlayerBase");
+
+ //==========================================================================================
+ // Mute conditional on device connection
+ //==========================================================================================
+ void muteAwaitConnection(@NonNull int[] usagesToMute,
+ @NonNull AudioDeviceAttributes dev, long timeOutMs) {
+ synchronized (mPlayerLock) {
+ mutePlayersExpectingDevice(usagesToMute);
+ // schedule timeout (remove previously scheduled first)
+ mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
+ mEventHandler.sendMessageDelayed(
+ mEventHandler.obtainMessage(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION, dev),
+ timeOutMs);
+ }
+ }
+
+ void cancelMuteAwaitConnection() {
+ synchronized (mPlayerLock) {
+ // cancel scheduled timeout, ignore device, only one expected device at a time
+ mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
+ // unmute immediately
+ unmutePlayersExpectingDevice();
+ }
+ }
+
+ /**
+ * List of the piids of the players that are muted until a specific audio device connects
+ */
+ @GuardedBy("mPlayerLock")
+ private final ArrayList<Integer> mMutedPlayersAwaitingConnection = new ArrayList<Integer>();
+
+ /**
+ * List of AudioAttributes usages to mute until a specific audio device connects
+ */
+ @GuardedBy("mPlayerLock")
+ private @Nullable int[] mMutedUsagesAwaitingConnection = null;
+
+ @GuardedBy("mPlayerLock")
+ private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
+ sEventLogger.log(new MuteAwaitConnectionEvent(usagesToMute));
+ mMutedUsagesAwaitingConnection = usagesToMute;
+ final Set<Integer> piidSet = mPlayers.keySet();
+ final Iterator<Integer> piidIterator = piidSet.iterator();
+ // find which players to mute
+ while (piidIterator.hasNext()) {
+ final Integer piid = piidIterator.next();
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ continue;
+ }
+ maybeMutePlayerAwaitingConnection(apc);
+ }
+ }
+
+ @GuardedBy("mPlayerLock")
+ private void maybeMutePlayerAwaitingConnection(@NonNull AudioPlaybackConfiguration apc) {
+ if (mMutedUsagesAwaitingConnection == null) {
+ return;
+ }
+ for (int usage : mMutedUsagesAwaitingConnection) {
+ if (usage == apc.getAudioAttributes().getUsage()) {
+ try {
+ sEventLogger.log((new AudioEventLogger.StringEvent(
+ "awaiting connection: muting piid:"
+ + apc.getPlayerInterfaceId()
+ + " uid:" + apc.getClientUid())).printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(
+ MUTE_AWAIT_CONNECTION_VSHAPE,
+ PLAY_CREATE_IF_NEEDED);
+ mMutedPlayersAwaitingConnection.add(apc.getPlayerInterfaceId());
+ } catch (Exception e) {
+ Log.e(TAG, "awaiting connection: error muting player "
+ + apc.getPlayerInterfaceId(), e);
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mPlayerLock")
+ private void unmutePlayersExpectingDevice() {
+ if (mMutedPlayersAwaitingConnection.isEmpty()) {
+ return;
+ }
+ for (int piid : mMutedPlayersAwaitingConnection) {
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ continue;
+ }
+ try {
+ sEventLogger.log(new AudioEventLogger.StringEvent(
+ "unmuting piid:" + piid).printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
+ VolumeShaper.Operation.REVERSE);
+ } catch (Exception e) {
+ Log.e(TAG, "Error unmuting player " + piid + " uid:"
+ + apc.getClientUid(), e);
+ }
+ }
+ mMutedPlayersAwaitingConnection.clear();
+ mMutedUsagesAwaitingConnection = null;
+ }
+
+ //=================================================================
+ // Message handling
+ private Handler mEventHandler;
+ private HandlerThread mEventThread;
+
+ /**
+ * timeout for a mute awaiting a device connection
+ * args:
+ * msg.obj: the audio device being expected
+ * type: AudioDeviceAttributes
+ */
+ private static final int MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION = 1;
+
+ private void initEventHandler() {
+ mEventThread = new HandlerThread(TAG);
+ mEventThread.start();
+ mEventHandler = new Handler(mEventThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
+ Log.i(TAG, "Timeout for muting waiting for "
+ + (AudioDeviceAttributes) msg.obj + ", unmuting");
+ synchronized (mPlayerLock) {
+ unmutePlayersExpectingDevice();
+ }
+ mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index f42870b..758cf7a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1036,7 +1036,8 @@
promptInfo.setAuthenticators(authenticators);
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
- userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */);
+ userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
+ getContext());
}
/**
@@ -1375,7 +1376,8 @@
try {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
- opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists());
+ opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
+ getContext());
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
@@ -1383,8 +1385,11 @@
+ "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
+ " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: "
+ promptInfo.isIgnoreEnrollmentState());
-
- if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
+ // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
+ // be shown for this case.
+ if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
+ || preAuthStatus.second
+ == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
// CREDENTIAL is requested and available, set the bundle to only request
// CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index a5a3542..05c3f68 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -26,6 +26,8 @@
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo;
@@ -59,6 +61,7 @@
static final int CREDENTIAL_NOT_ENROLLED = 9;
static final int BIOMETRIC_LOCKOUT_TIMED = 10;
static final int BIOMETRIC_LOCKOUT_PERMANENT = 11;
+ static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12;
@IntDef({AUTHENTICATOR_OK,
BIOMETRIC_NO_HARDWARE,
BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
@@ -69,7 +72,8 @@
BIOMETRIC_NOT_ENABLED_FOR_APPS,
CREDENTIAL_NOT_ENROLLED,
BIOMETRIC_LOCKOUT_TIMED,
- BIOMETRIC_LOCKOUT_PERMANENT})
+ BIOMETRIC_LOCKOUT_PERMANENT,
+ BIOMETRIC_SENSOR_PRIVACY_ENABLED})
@Retention(RetentionPolicy.SOURCE)
@interface AuthenticatorStatus {}
@@ -84,13 +88,15 @@
final boolean credentialAvailable;
final boolean confirmationRequested;
final boolean ignoreEnrollmentState;
+ final int userId;
+ final Context context;
static PreAuthInfo create(ITrustManager trustManager,
DevicePolicyManager devicePolicyManager,
BiometricService.SettingObserver settingObserver,
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
- boolean checkDevicePolicyManager)
+ boolean checkDevicePolicyManager, Context context)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -116,14 +122,22 @@
devicePolicyManager, settingObserver, sensor, userId, opPackageName,
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
- promptInfo.isIgnoreEnrollmentState());
+ promptInfo.isIgnoreEnrollmentState(),
+ context);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
+ " Modality: " + sensor.modality
+ " Status: " + status);
- if (status == AUTHENTICATOR_OK) {
+ // A sensor with privacy enabled will still be eligible to
+ // authenticate with biometric prompt. This is so the framework can display
+ // a sensor privacy error message to users after briefly showing the
+ // Biometric Prompt.
+ //
+ // Note: if only a certain sensor is required and the privacy is enabled,
+ // canAuthenticate() will return false.
+ if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
eligibleSensors.add(sensor);
} else {
ineligibleSensors.add(new Pair<>(sensor, status));
@@ -133,7 +147,7 @@
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState());
+ promptInfo.isIgnoreEnrollmentState(), userId, context);
}
/**
@@ -149,7 +163,7 @@
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState) {
+ boolean ignoreEnrollmentState, Context context) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
@@ -175,6 +189,16 @@
&& !ignoreEnrollmentState) {
return BIOMETRIC_NOT_ENROLLED;
}
+ final SensorPrivacyManager sensorPrivacyManager = context
+ .getSystemService(SensorPrivacyManager.class);
+
+ if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
+ if (sensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+ return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ }
+ }
+
final @LockoutTracker.LockoutMode int lockoutMode =
sensor.impl.getLockoutModeForUser(userId);
@@ -243,7 +267,8 @@
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
- boolean confirmationRequested, boolean ignoreEnrollmentState) {
+ boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
+ Context context) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
this.credentialRequested = credentialRequested;
@@ -253,6 +278,8 @@
this.credentialAvailable = credentialAvailable;
this.confirmationRequested = confirmationRequested;
this.ignoreEnrollmentState = ignoreEnrollmentState;
+ this.userId = userId;
+ this.context = context;
}
private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
@@ -280,15 +307,35 @@
private Pair<Integer, Integer> getInternalStatus() {
@AuthenticatorStatus final int status;
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
+
+ final SensorPrivacyManager sensorPrivacyManager = context
+ .getSystemService(SensorPrivacyManager.class);
+
+ boolean cameraPrivacyEnabled = false;
+ if (sensorPrivacyManager != null) {
+ cameraPrivacyEnabled = sensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+ }
+
if (mBiometricRequested && credentialRequested) {
if (credentialAvailable || !eligibleSensors.isEmpty()) {
- status = AUTHENTICATOR_OK;
- if (credentialAvailable) {
- modality |= TYPE_CREDENTIAL;
- }
for (BiometricSensor sensor : eligibleSensors) {
modality |= sensor.modality;
}
+
+ if (credentialAvailable) {
+ modality |= TYPE_CREDENTIAL;
+ status = AUTHENTICATOR_OK;
+ } else if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+ // If the only modality requested is face, credential is unavailable,
+ // and the face sensor privacy is enabled then return
+ // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+ //
+ // Note: This sensor will still be eligible for calls to authenticate.
+ status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ } else {
+ status = AUTHENTICATOR_OK;
+ }
} else {
// Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) {
@@ -302,10 +349,18 @@
}
} else if (mBiometricRequested) {
if (!eligibleSensors.isEmpty()) {
- status = AUTHENTICATOR_OK;
- for (BiometricSensor sensor : eligibleSensors) {
- modality |= sensor.modality;
- }
+ for (BiometricSensor sensor : eligibleSensors) {
+ modality |= sensor.modality;
+ }
+ if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+ // If the only modality requested is face and the privacy is enabled
+ // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+ //
+ // Note: This sensor will still be eligible for calls to authenticate.
+ status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ } else {
+ status = AUTHENTICATOR_OK;
+ }
} else {
// Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) {
@@ -326,9 +381,9 @@
Slog.e(TAG, "No authenticators requested");
status = BIOMETRIC_NO_HARDWARE;
}
-
Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality
+ " AuthenticatorStatus: " + status);
+
return new Pair<>(modality, status);
}
@@ -362,6 +417,7 @@
case CREDENTIAL_NOT_ENROLLED:
case BIOMETRIC_LOCKOUT_TIMED:
case BIOMETRIC_LOCKOUT_PERMANENT:
+ case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
break;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 4f7c6b0..0e2582c 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -33,6 +33,7 @@
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
+import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED;
import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
import android.annotation.NonNull;
@@ -278,6 +279,9 @@
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
break;
+ case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ break;
default:
Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
@@ -337,7 +341,8 @@
case BIOMETRIC_LOCKOUT_PERMANENT:
return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-
+ case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
+ return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
case BIOMETRIC_HARDWARE_NOT_DETECTED:
case BIOMETRIC_NOT_ENABLED_FOR_APPS:
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 97d791b..4131ae1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -21,6 +21,7 @@
import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -56,6 +57,7 @@
@NonNull private final LockoutCache mLockoutCache;
@Nullable private final NotificationManager mNotificationManager;
@Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable private SensorPrivacyManager mSensorPrivacyManager;
private final int[] mBiometricPromptIgnoreList;
private final int[] mBiometricPromptIgnoreListVendor;
@@ -81,6 +83,7 @@
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -108,7 +111,16 @@
@Override
protected void startHalOperation() {
try {
- mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+ getTargetUserId())) {
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ } else {
+ mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 2ef0911..2158dfe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.ISession;
@@ -41,6 +43,7 @@
private final boolean mIsStrongBiometric;
@Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable private SensorPrivacyManager mSensorPrivacyManager;
public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@NonNull IBinder token, long requestId,
@@ -51,6 +54,7 @@
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
}
@Override
@@ -73,6 +77,14 @@
@Override
protected void startHalOperation() {
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
try {
mCancellationSignal = getFreshDaemon().detectInteraction();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 40f2801..7548d28 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -55,6 +56,7 @@
private final int[] mKeyguardIgnoreListVendor;
private int mLastAcquire;
+ private SensorPrivacyManager mSensorPrivacyManager;
FaceAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
@@ -71,6 +73,7 @@
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -97,6 +100,15 @@
@Override
protected void startHalOperation() {
+
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+ onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
try {
getFreshDaemon().authenticate(mOperationId);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java
index 1196442..8d9b13e 100644
--- a/services/core/java/com/android/server/communal/CommunalManagerService.java
+++ b/services/core/java/com/android/server/communal/CommunalManagerService.java
@@ -84,6 +84,10 @@
@Override
public Intent intercept(ActivityInterceptorInfo info) {
if (!shouldIntercept(info.aInfo)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Activity allowed, not intercepting: "
+ + info.aInfo.getComponentName());
+ }
return null;
}
@@ -188,8 +192,17 @@
return true;
}
- return isChangeEnabled(ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT, appInfo)
- && getUserEnabledApps().contains(appInfo.packageName);
+ if (!isChangeEnabled(ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT, appInfo)) {
+ if (DEBUG) Slog.d(TAG, "App is not allowlisted: " + appInfo.packageName);
+ return false;
+ }
+
+ if (!getUserEnabledApps().contains(appInfo.packageName)) {
+ if (DEBUG) Slog.d(TAG, "App does not have user consent: " + appInfo.packageName);
+ return false;
+ }
+
+ return true;
}
private boolean isActiveDream(ApplicationInfo appInfo) {
@@ -219,6 +232,10 @@
final boolean showWhenLocked =
(activityInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0;
if (!showWhenLocked) {
+ if (DEBUG) {
+ Slog.d(TAG, "Activity does not contain showWhenLocked attribute: "
+ + activityInfo.getComponentName());
+ }
return true;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 26a5bbb..356d6c9 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -286,6 +286,7 @@
synchronized (mSessions) {
readSessionsLocked();
+ expireSessionsLocked();
reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL);
@@ -462,34 +463,7 @@
Slog.e(TAG, "Could not read session", e);
continue;
}
-
- final long age = System.currentTimeMillis() - session.createdMillis;
- final long timeSinceUpdate =
- System.currentTimeMillis() - session.getUpdatedMillis();
- final boolean valid;
- if (session.isStaged()) {
- if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS
- && session.isStagedAndInTerminalState()) {
- valid = false;
- } else {
- valid = true;
- }
- } else if (age >= MAX_AGE_MILLIS) {
- Slog.w(TAG, "Abandoning old session created at "
- + session.createdMillis);
- valid = false;
- } else {
- valid = true;
- }
-
- if (valid) {
- mSessions.put(session.sessionId, session);
- } else {
- // Since this is early during boot we don't send
- // any observer events about the session, but we
- // keep details around for dumpsys.
- addHistoricalSessionLocked(session);
- }
+ mSessions.put(session.sessionId, session);
mAllocatedSessions.put(session.sessionId, true);
}
}
@@ -509,6 +483,44 @@
}
@GuardedBy("mSessions")
+ private void expireSessionsLocked() {
+ SparseArray<PackageInstallerSession> tmp = mSessions.clone();
+ final int n = tmp.size();
+ for (int i = 0; i < n; ++i) {
+ PackageInstallerSession session = tmp.valueAt(i);
+ if (session.hasParentSessionId()) {
+ // Child sessions will be expired when handling parent sessions
+ continue;
+ }
+ final long age = System.currentTimeMillis() - session.createdMillis;
+ final long timeSinceUpdate = System.currentTimeMillis() - session.getUpdatedMillis();
+ final boolean valid;
+ if (session.isStaged()) {
+ valid = !session.isStagedAndInTerminalState()
+ || timeSinceUpdate < MAX_TIME_SINCE_UPDATE_MILLIS;
+ } else if (age >= MAX_AGE_MILLIS) {
+ Slog.w(TAG, "Abandoning old session created at "
+ + session.createdMillis);
+ valid = false;
+ } else {
+ valid = true;
+ }
+ if (!valid) {
+ // Remove expired sessions as well as child sessions if any
+ mSessions.remove(session.sessionId);
+ // Since this is early during boot we don't send
+ // any observer events about the session, but we
+ // keep details around for dumpsys.
+ addHistoricalSessionLocked(session);
+ for (PackageInstallerSession child : session.getChildSessions()) {
+ mSessions.remove(child.sessionId);
+ addHistoricalSessionLocked(child);
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mSessions")
private void addHistoricalSessionLocked(PackageInstallerSession session) {
CharArrayWriter writer = new CharArrayWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 328a55f..0f3b4bc 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -143,8 +143,9 @@
UserManager.DISALLOW_CAMERA_TOGGLE,
UserManager.DISALLOW_CHANGE_WIFI_STATE,
UserManager.DISALLOW_WIFI_TETHERING,
- UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI
-
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -190,7 +191,9 @@
UserManager.DISALLOW_MICROPHONE_TOGGLE,
UserManager.DISALLOW_CAMERA_TOGGLE,
UserManager.DISALLOW_CHANGE_WIFI_STATE,
- UserManager.DISALLOW_WIFI_TETHERING
+ UserManager.DISALLOW_WIFI_TETHERING,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
);
/**
@@ -227,7 +230,9 @@
UserManager.DISALLOW_CONFIG_DATE_TIME,
UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
UserManager.DISALLOW_CHANGE_WIFI_STATE,
- UserManager.DISALLOW_WIFI_TETHERING
+ UserManager.DISALLOW_WIFI_TETHERING,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
);
/**
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 232ea09..4eae939 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -64,6 +64,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -139,6 +140,8 @@
private int mCurrentUserId;
private boolean mTracingEnabled;
+ private final TileRequestTracker mTileRequestTracker;
+
private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
@GuardedBy("mLock")
private IUdfpsHbmListener mUdfpsHbmListener;
@@ -245,6 +248,8 @@
mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+ mTileRequestTracker = new TileRequestTracker(mContext);
}
@Override
@@ -1765,11 +1770,26 @@
mCurrentRequestAddTilePackages.put(packageName, currentTime);
}
+ if (mTileRequestTracker.shouldBeDenied(userId, componentName)) {
+ if (clearTileAddRequest(packageName)) {
+ try {
+ callback.onTileRequest(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "requestAddTile - callback", e);
+ }
+ }
+ return;
+ }
+
IAddTileResultCallback proxyCallback = new IAddTileResultCallback.Stub() {
@Override
public void onTileRequest(int i) {
if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED) {
i = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED;
+ } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED) {
+ mTileRequestTracker.addDenial(userId, componentName);
+ } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED) {
+ mTileRequestTracker.resetRequests(userId, componentName);
}
if (clearTileAddRequest(packageName)) {
try {
@@ -1961,6 +1981,8 @@
pw.println(" " + requests.get(i) + ",");
}
pw.println(" ]");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ mTileRequestTracker.dump(fd, ipw.increaseIndent(), args);
}
}
diff --git a/services/core/java/com/android/server/statusbar/TileRequestTracker.java b/services/core/java/com/android/server/statusbar/TileRequestTracker.java
new file mode 100644
index 0000000..d5ace3f7
--- /dev/null
+++ b/services/core/java/com/android/server/statusbar/TileRequestTracker.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+
+/**
+ * Tracks user denials of requests from {@link StatusBarManagerService#requestAddTile}.
+ *
+ * After a certain number of denials for a particular pair (user,ComponentName), requests will be
+ * auto-denied without showing a dialog to the user.
+ */
+public class TileRequestTracker {
+
+ @VisibleForTesting
+ static final int MAX_NUM_DENIALS = 3;
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final SparseArrayMap<ComponentName, Integer> mTrackingMap = new SparseArrayMap<>();
+ @GuardedBy("mLock")
+ private final ArraySet<ComponentName> mComponentsToRemove = new ArraySet<>();
+
+ private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ return;
+ }
+
+ Uri data = intent.getData();
+ String packageName = data.getEncodedSchemeSpecificPart();
+
+ if (!intent.hasExtra(Intent.EXTRA_UID)) {
+ return;
+ }
+ int userId = UserHandle.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1));
+ synchronized (mLock) {
+ mComponentsToRemove.clear();
+ final int elementsForUser = mTrackingMap.numElementsForKey(userId);
+ final int userKeyIndex = mTrackingMap.indexOfKey(userId);
+ for (int compKeyIndex = 0; compKeyIndex < elementsForUser; compKeyIndex++) {
+ ComponentName c = mTrackingMap.keyAt(userKeyIndex, compKeyIndex);
+ if (c.getPackageName().equals(packageName)) {
+ mComponentsToRemove.add(c);
+ }
+ }
+ final int compsToRemoveNum = mComponentsToRemove.size();
+ for (int i = 0; i < compsToRemoveNum; i++) {
+ ComponentName c = mComponentsToRemove.valueAt(i);
+ mTrackingMap.delete(userId, c);
+ }
+ }
+ }
+ };
+
+ TileRequestTracker(Context context) {
+ mContext = context;
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mUninstallReceiver, UserHandle.ALL, intentFilter, null,
+ null);
+ }
+
+ /**
+ * Return whether this combination of {@code userId} and {@link ComponentName} should be
+ * auto-denied.
+ */
+ boolean shouldBeDenied(int userId, ComponentName componentName) {
+ synchronized (mLock) {
+ return mTrackingMap.getOrDefault(userId, componentName, 0) >= MAX_NUM_DENIALS;
+ }
+ }
+
+ /**
+ * Add a new denial instance for a given {@code userId} and {@link ComponentName}.
+ */
+ void addDenial(int userId, ComponentName componentName) {
+ synchronized (mLock) {
+ int current = mTrackingMap.getOrDefault(userId, componentName, 0);
+ mTrackingMap.add(userId, componentName, current + 1);
+ }
+ }
+
+ /**
+ * Reset the number of denied request for a given {@code userId} and {@link ComponentName}.
+ */
+ void resetRequests(int userId, ComponentName componentName) {
+ synchronized (mLock) {
+ mTrackingMap.delete(userId, componentName);
+ }
+ }
+
+ void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ pw.println("TileRequestTracker:");
+ pw.increaseIndent();
+ synchronized (mLock) {
+ mTrackingMap.forEach((user, componentName, value) -> {
+ pw.println("user=" + user + ", " + componentName.toShortString() + ": " + value);
+ });
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 65907f1..417177f 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -35,6 +35,7 @@
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
+import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -179,8 +180,13 @@
public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED =
"enhanced_metrics_collection_enabled";
+ /**
+ * The registered listeners and the keys to trigger on. The value is explicitly a HashSet to
+ * ensure O(1) lookup performance when working out whether a listener should trigger.
+ */
@GuardedBy("mListeners")
- private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();
+ private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
+ new ArrayMap<>();
private static final Object SLOCK = new Object();
@@ -207,18 +213,29 @@
private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
synchronized (mListeners) {
- for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry
+ for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
: mListeners.entrySet()) {
- if (intersects(listenerEntry.getValue(), properties.getKeyset())) {
+ // It's unclear which set of the following two Sets is going to be larger in the
+ // average case: monitoredKeys will be a subset of the set of possible keys, but
+ // only changed keys are reported. Because we guarantee the type / lookup behavior
+ // of the monitoredKeys by making that a HashSet, that is used as the haystack Set,
+ // while the changed keys is treated as the needles Iterable. At the time of
+ // writing, properties.getKeyset() actually returns a HashSet, so iteration isn't
+ // super efficient and the use of HashSet for monitoredKeys may be redundant, but
+ // neither set will be enormous.
+ HashSet<String> monitoredKeys = listenerEntry.getValue();
+ Iterable<String> modifiedKeys = properties.getKeyset();
+ if (containsAny(monitoredKeys, modifiedKeys)) {
listenerEntry.getKey().onChange();
}
}
}
}
- private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) {
- for (String toFind : one) {
- if (two.contains(toFind)) {
+ private static boolean containsAny(
+ @NonNull Set<String> haystack, @NonNull Iterable<String> needles) {
+ for (String needle : needles) {
+ if (haystack.contains(needle)) {
return true;
}
}
@@ -237,8 +254,11 @@
Objects.requireNonNull(listener);
Objects.requireNonNull(keys);
+ // Make a defensive copy and use a well-defined Set implementation to provide predictable
+ // performance on the lookup.
+ HashSet<String> keysCopy = new HashSet<>(keys);
synchronized (mListeners) {
- mListeners.put(listener, keys);
+ mListeners.put(listener, keysCopy);
}
}
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index 7f7d01c..5f14000 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.os.Build;
import android.os.SystemProperties;
-import android.util.ArraySet;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -35,7 +34,6 @@
import java.time.Instant;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -65,11 +63,10 @@
Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
/** Device config keys that affect the {@link TimeDetectorService}. */
- private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(
- new ArraySet<>(new String[] {
- KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
- KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
- }));
+ private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+ KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
+ KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE
+ );
private static final Object SLOCK = new Object();
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index b452d90..ae52912 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -36,7 +36,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.util.ArraySet;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -45,7 +44,6 @@
import java.time.Duration;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -59,33 +57,31 @@
/**
* Device config keys that can affect the content of {@link ConfigurationInternal}.
*/
- private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH =
- Collections.unmodifiableSet(new ArraySet<>(new String[] {
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
- ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
- ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
- }));
+ private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
+ ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
+ ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED
+ );
/**
* Device config keys that can affect {@link
* com.android.server.timezonedetector.location.LocationTimeZoneManagerService} behavior.
*/
- private static final Set<String> LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH =
- Collections.unmodifiableSet(new ArraySet<>(new String[] {
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
- ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE,
- ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE,
- ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
- ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
- ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
- }));
+ private static final Set<String> LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
+ ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE,
+ ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE,
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
+ );
private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 0436460..6628802 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -37,6 +37,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
@@ -52,6 +53,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
/**
* This class provides a system service that manages the TV tuner resources.
@@ -64,6 +68,8 @@
public static final int INVALID_CLIENT_ID = -1;
private static final int MAX_CLIENT_PRIORITY = 1000;
+ private static final long INVALID_THREAD_ID = -1;
+ private static final long TRMS_LOCK_TIMEOUT = 500;
// Map of the registered client profiles
private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
@@ -94,6 +100,12 @@
// Used to synchronize the access to the service.
private final Object mLock = new Object();
+ private final ReentrantLock mLockForTRMSLock = new ReentrantLock();
+ private final Condition mTunerApiLockReleasedCV = mLockForTRMSLock.newCondition();
+ private int mTunerApiLockHolder = INVALID_CLIENT_ID;
+ private long mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
+ private int mTunerApiLockNestedCount = 0;
+
public TunerResourceManagerService(@Nullable Context context) {
super(context);
}
@@ -511,6 +523,20 @@
}
@Override
+ public boolean acquireLock(int clientId, long clientThreadId) {
+ enforceTrmAccessPermission("acquireLock");
+ // this must not be locked with mLock
+ return acquireLockInternal(clientId, clientThreadId, TRMS_LOCK_TIMEOUT);
+ }
+
+ @Override
+ public boolean releaseLock(int clientId) {
+ enforceTrmAccessPermission("releaseLock");
+ // this must not be locked with mLock
+ return releaseLockInternal(clientId, TRMS_LOCK_TIMEOUT, false, false);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1194,6 +1220,187 @@
return true;
}
+ // Return value is guaranteed to be positive
+ private long getElapsedTime(long begin) {
+ long now = SystemClock.uptimeMillis();
+ long elapsed;
+ if (now >= begin) {
+ elapsed = now - begin;
+ } else {
+ elapsed = Long.MAX_VALUE - begin + now;
+ if (elapsed < 0) {
+ elapsed = Long.MAX_VALUE;
+ }
+ }
+ return elapsed;
+ }
+
+ private boolean lockForTunerApiLock(int clientId, long timeoutMS, String callerFunction) {
+ try {
+ if (mLockForTRMSLock.tryLock(timeoutMS, TimeUnit.MILLISECONDS)) {
+ return true;
+ } else {
+ Slog.e(TAG, "FAILED to lock mLockForTRMSLock in " + callerFunction
+ + ", clientId:" + clientId + ", timeoutMS:" + timeoutMS
+ + ", mTunerApiLockHolder:" + mTunerApiLockHolder);
+ return false;
+ }
+ } catch (InterruptedException ie) {
+ Slog.e(TAG, "exception thrown in " + callerFunction + ":" + ie);
+ if (mLockForTRMSLock.isHeldByCurrentThread()) {
+ mLockForTRMSLock.unlock();
+ }
+ return false;
+ }
+ }
+
+ private boolean acquireLockInternal(int clientId, long clientThreadId, long timeoutMS) {
+ long begin = SystemClock.uptimeMillis();
+
+ // Grab lock
+ if (!lockForTunerApiLock(clientId, timeoutMS, "acquireLockInternal()")) {
+ return false;
+ }
+
+ try {
+ boolean available = mTunerApiLockHolder == INVALID_CLIENT_ID;
+ boolean nestedSelf = (clientId == mTunerApiLockHolder)
+ && (clientThreadId == mTunerApiLockHolderThreadId);
+ boolean recovery = false;
+
+ // Allow same thread to grab the lock multiple times
+ while (!available && !nestedSelf) {
+ // calculate how much time is left before timeout
+ long leftOverMS = timeoutMS - getElapsedTime(begin);
+ if (leftOverMS <= 0) {
+ Slog.e(TAG, "FAILED:acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ") - timed out, but will grant the lock to "
+ + "the callee by stealing it from the current holder:"
+ + mTunerApiLockHolder + "(" + mTunerApiLockHolderThreadId + "), "
+ + "who likely failed to call releaseLock(), "
+ + "to prevent this from becoming an unrecoverable error");
+ // This should not normally happen, but there sometimes are cases where
+ // in-flight tuner API execution gets scheduled even after binderDied(),
+ // which can leave the in-flight execution dissappear/stopped in between
+ // acquireLock and releaseLock
+ recovery = true;
+ break;
+ }
+
+ // Cond wait for left over time
+ mTunerApiLockReleasedCV.await(leftOverMS, TimeUnit.MILLISECONDS);
+
+ // Check the availability for "spurious wakeup"
+ // The case that was confirmed is that someone else can acquire this in between
+ // signal() and wakup from the above await()
+ available = mTunerApiLockHolder == INVALID_CLIENT_ID;
+
+ if (!available) {
+ Slog.w(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + ", "
+ + timeoutMS + ") - woken up from cond wait, but " + mTunerApiLockHolder
+ + "(" + mTunerApiLockHolderThreadId + ") is already holding the lock. "
+ + "Going to wait again if timeout hasn't reached yet");
+ }
+ }
+
+ // Will always grant unless exception is thrown (or lock is already held)
+ if (available || recovery) {
+ if (DEBUG) {
+ Slog.d(TAG, "SUCCESS:acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ")");
+ }
+
+ if (mTunerApiLockNestedCount != 0) {
+ Slog.w(TAG, "Something is wrong as nestedCount(" + mTunerApiLockNestedCount
+ + ") is not zero. Will overriding it to 1 anyways");
+ }
+
+ // set the caller to be the holder
+ mTunerApiLockHolder = clientId;
+ mTunerApiLockHolderThreadId = clientThreadId;
+ mTunerApiLockNestedCount = 1;
+ } else if (nestedSelf) {
+ // Increment the nested count so releaseLockInternal won't signal prematuredly
+ mTunerApiLockNestedCount++;
+ if (DEBUG) {
+ Slog.d(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ") - nested count incremented to "
+ + mTunerApiLockNestedCount);
+ }
+ } else {
+ Slog.e(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ") - should not reach here");
+ }
+ // return true in "recovery" so callee knows that the deadlock is possible
+ // only when the return value is false
+ return (available || nestedSelf || recovery);
+ } catch (InterruptedException ie) {
+ Slog.e(TAG, "exception thrown in acquireLockInternal(" + clientId + ", "
+ + clientThreadId + ", " + timeoutMS + "):" + ie);
+ return false;
+ } finally {
+ if (mLockForTRMSLock.isHeldByCurrentThread()) {
+ mLockForTRMSLock.unlock();
+ }
+ }
+ }
+
+ private boolean releaseLockInternal(int clientId, long timeoutMS,
+ boolean ignoreNestedCount, boolean suppressError) {
+ // Grab lock first
+ if (!lockForTunerApiLock(clientId, timeoutMS, "releaseLockInternal()")) {
+ return false;
+ }
+
+ try {
+ if (mTunerApiLockHolder == clientId) {
+ // Should always reach here unless called from binderDied()
+ mTunerApiLockNestedCount--;
+ if (ignoreNestedCount || mTunerApiLockNestedCount <= 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "SUCCESS:releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ", " + ignoreNestedCount + ", " + suppressError
+ + ") - signaling!");
+ }
+ // Reset the current holder and signal
+ mTunerApiLockHolder = INVALID_CLIENT_ID;
+ mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
+ mTunerApiLockNestedCount = 0;
+ mTunerApiLockReleasedCV.signal();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ", " + ignoreNestedCount + ", " + suppressError
+ + ") - NOT signaling because nested count is not zero ("
+ + mTunerApiLockNestedCount + ")");
+ }
+ }
+ return true;
+ } else if (mTunerApiLockHolder == INVALID_CLIENT_ID) {
+ if (!suppressError) {
+ Slog.w(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ") - called while there is no current holder");
+ }
+ // No need to do anything.
+ // Shouldn't reach here unless called from binderDied()
+ return false;
+ } else {
+ if (!suppressError) {
+ Slog.e(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ") - called while someone else:" + mTunerApiLockHolder
+ + "is the current holder");
+ }
+ // Cannot reset the holder Id because it reaches here when called
+ // from binderDied()
+ return false;
+ }
+ } finally {
+ if (mLockForTRMSLock.isHeldByCurrentThread()) {
+ mLockForTRMSLock.unlock();
+ }
+ }
+ }
+
@VisibleForTesting
protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
private final IResourcesReclaimListener mListener;
@@ -1206,10 +1413,15 @@
@Override
public void binderDied() {
- synchronized (mLock) {
- if (checkClientExists(mClientId)) {
- removeClientProfile(mClientId);
+ try {
+ synchronized (mLock) {
+ if (checkClientExists(mClientId)) {
+ removeClientProfile(mClientId);
+ }
}
+ } finally {
+ // reset the tuner API lock
+ releaseLockInternal(mClientId, TRMS_LOCK_TIMEOUT, true, true);
}
}
@@ -1247,6 +1459,13 @@
protected boolean reclaimResource(int reclaimingClientId,
@TunerResourceManager.TunerResourceType int resourceType) {
+ // Allowing this because:
+ // 1) serialization of resource reclaim is required in the current design
+ // 2) the outgoing transaction is handled by the system app (with
+ // android.Manifest.permission.TUNER_RESOURCE_ACCESS), which goes through full
+ // Google certification
+ Binder.allowBlockingForCurrentThread();
+
// Reclaim all the resources of the share owners of the frontend that is used by the current
// resource reclaimed client.
ClientProfile profile = getClientProfile(reclaimingClientId);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 03d6590..d5abe4f 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1426,21 +1426,21 @@
}
@TransitionType int getKeyguardTransition() {
- // In case we unocclude Keyguard and occlude it again, meaning that we never actually
- // unoccclude/occlude Keyguard, but just run a normal transition.
- final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
- if (occludeIndex != -1
- && occludeIndex < mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE)) {
+ if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) {
+ return TRANSIT_KEYGUARD_GOING_AWAY;
+ }
+ final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
+ final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE);
+ // No keyguard related transition requests.
+ if (unoccludeIndex == -1 && occludeIndex == -1) {
return TRANSIT_NONE;
}
-
- for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) {
- final @TransitionType int transit = mNextAppTransitionRequests.get(i);
- if (isKeyguardTransit(transit)) {
- return transit;
- }
+ // In case we unocclude Keyguard and occlude it again, meaning that we never actually
+ // unoccclude/occlude Keyguard, but just run a normal transition.
+ if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) {
+ return TRANSIT_NONE;
}
- return TRANSIT_NONE;
+ return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE;
}
@TransitionType int getFirstAppTransition() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 71ab5b6..4768b27 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -45,7 +45,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -846,20 +845,6 @@
}
/**
- * Only trusted overlays are allowed to use FLAG_SLIPPERY.
- */
- static int sanitizeFlagSlippery(int flags, int privateFlags, String name) {
- if ((flags & FLAG_SLIPPERY) == 0) {
- return flags;
- }
- if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
- return flags;
- }
- Slog.w(TAG, "Removing FLAG_SLIPPERY for non-trusted overlay " + name);
- return flags & ~FLAG_SLIPPERY;
- }
-
- /**
* Sanitize the layout parameters coming from a client. Allows the policy
* to do things like ensure that windows of a specific type can't take
* input focus.
@@ -942,8 +927,6 @@
} else if (mRoundedCornerWindow == win) {
mRoundedCornerWindow = null;
}
-
- attrs.flags = sanitizeFlagSlippery(attrs.flags, attrs.privateFlags, win.getName());
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ea99781..038699b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4534,14 +4534,15 @@
}
super.setWindowingMode(windowingMode);
- // Try reparent pinned activity back to its original task after onConfigurationChanged
- // cascade finishes. This is done on Task level instead of
- // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
- // we set final windowing mode on the ActivityRecord first and then on its Task when
- // the exit PiP transition finishes. Meanwhile, the exit transition is always
- // performed on its original task, reparent immediately in ActivityRecord breaks it.
- if (currentMode == WINDOWING_MODE_PINNED) {
- if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+ if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) {
+ // Try reparent pinned activity back to its original task after
+ // onConfigurationChanged cascade finishes. This is done on Task level instead of
+ // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit
+ // PiP, we set final windowing mode on the ActivityRecord first and then on its
+ // Task when the exit PiP transition finishes. Meanwhile, the exit transition is
+ // always performed on its original task, reparent immediately in ActivityRecord
+ // breaks it.
+ if (topActivity.getLastParentBeforePip() != null) {
// Do not reparent if the pinned task is in removal, indicated by the
// force hidden flag.
if (!isForceHidden()) {
@@ -4554,6 +4555,11 @@
}
}
}
+ // Resume app-switches-allowed flag when exiting from pinned mode since
+ // it does not follow the ActivityStarter path.
+ if (topActivity.shouldBeVisible()) {
+ mAtmService.resumeAppSwitches();
+ }
}
if (creating) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5eabe75..fdaa2fc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2187,14 +2187,13 @@
TaskFragmentInfo getTaskFragmentInfo() {
List<IBinder> childActivities = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
- WindowContainer wc = getChildAt(i);
- if (mTaskFragmentOrganizerUid != INVALID_UID
- && wc.asActivityRecord() != null
- && wc.asActivityRecord().info.processName.equals(
- mTaskFragmentOrganizerProcessName)
- && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) {
+ final WindowContainer wc = getChildAt(i);
+ final ActivityRecord ar = wc.asActivityRecord();
+ if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null
+ && ar.info.processName.equals(mTaskFragmentOrganizerProcessName)
+ && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) {
// Only includes Activities that belong to the organizer process for security.
- childActivities.add(wc.asActivityRecord().token);
+ childActivities.add(ar.token);
}
}
final Point positionInParent = new Point();
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 29c27f9..c7fdefc 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -24,6 +24,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -579,4 +580,26 @@
event.mException);
}
}
+
+ // TODO(b/204399167): change to push the embedded state to the client side
+ @Override
+ public boolean isActivityEmbedded(IBinder activityToken) {
+ synchronized (mGlobalLock) {
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
+ if (activity == null) {
+ return false;
+ }
+ final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
+ if (taskFragment == null) {
+ return false;
+ }
+ final Task parentTask = taskFragment.getTask();
+ if (parentTask != null) {
+ final Rect taskBounds = parentTask.getBounds();
+ final Rect taskFragBounds = taskFragment.getBounds();
+ return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+ }
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e986fd2..58860de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1652,6 +1652,7 @@
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
win.setRequestedVisibilities(requestedVisibilities);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
@@ -2207,6 +2208,7 @@
if (attrs != null) {
displayPolicy.adjustWindowParamsLw(win, attrs);
win.mToken.adjustWindowParams(win, attrs);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
int disableFlags =
(attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility) & DISABLE_MASK;
if (disableFlags != 0 && !hasStatusBarPermission(pid, uid)) {
@@ -8220,6 +8222,23 @@
}
/**
+ * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
+ */
+ private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
+ if ((flags & FLAG_SLIPPERY) == 0) {
+ return flags;
+ }
+ final int permissionResult = mContext.checkPermission(
+ android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid);
+ if (permissionResult != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName
+ + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission");
+ return flags & ~FLAG_SLIPPERY;
+ }
+ return flags;
+ }
+
+ /**
* Assigns an InputChannel to a SurfaceControl and configures it to receive
* touch input according to it's on-screen geometry.
*
@@ -8258,7 +8277,7 @@
h.setWindowToken(window);
h.name = name;
- flags = DisplayPolicy.sanitizeFlagSlippery(flags, privateFlags, name);
+ flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
| FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE);
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 029254c..06f5aed 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -923,6 +923,7 @@
gnss::GnssAntennaInfo_class_init_once(env, clazz);
gnss::GnssBatching_class_init_once(env, clazz);
gnss::GnssConfiguration_class_init_once(env);
+ gnss::GnssGeofence_class_init_once(env, clazz);
gnss::GnssMeasurement_class_init_once(env, clazz);
gnss::GnssNavigationMessage_class_init_once(env, clazz);
gnss::AGnss_class_init_once(env, clazz);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 98a7b5e..bfceeda 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9008,42 +9008,48 @@
return;
}
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
final CallerIdentity caller = getCallerIdentity();
- if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
- && getManagedUserId(userHandle) == -1
- && newState != STATE_USER_UNMANAGED) {
- // No managed device, user or profile, so setting provisioning state makes no sense.
- throw new IllegalStateException("Not allowed to change provisioning state unless a "
- + "device or profile owner is set.");
- }
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
+ && getManagedUserId(userHandle) == -1
+ && newState != STATE_USER_UNMANAGED) {
+ // No managed device, user or profile, so setting provisioning state makes no sense.
+ throw new IllegalStateException("Not allowed to change provisioning state unless a "
+ + "device or profile owner is set.");
+ }
- synchronized (getLockObject()) {
- boolean transitionCheckNeeded = true;
+ synchronized (getLockObject()) {
+ boolean transitionCheckNeeded = true;
- // Calling identity/permission checks.
- if (isAdb(caller)) {
- // ADB shell can only move directly from un-managed to finalized as part of directly
- // setting profile-owner or device-owner.
- if (getUserProvisioningState(userHandle) !=
- DevicePolicyManager.STATE_USER_UNMANAGED
- || newState != DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
- throw new IllegalStateException("Not allowed to change provisioning state "
- + "unless current provisioning state is unmanaged, and new state is "
- + "finalized.");
+ // Calling identity/permission checks.
+ if (isAdb(caller)) {
+ // ADB shell can only move directly from un-managed to finalized as part of
+ // directly setting profile-owner or device-owner.
+ if (getUserProvisioningState(userHandle)
+ != DevicePolicyManager.STATE_USER_UNMANAGED
+ || newState != DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
+ throw new IllegalStateException("Not allowed to change provisioning state "
+ + "unless current provisioning state is unmanaged, and new state"
+ + "is finalized.");
+ }
+ transitionCheckNeeded = false;
}
- transitionCheckNeeded = false;
- } else {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
- }
- final DevicePolicyData policyData = getUserData(userHandle);
- if (transitionCheckNeeded) {
- // Optional state transition check for non-ADB case.
- checkUserProvisioningStateTransition(policyData.mUserProvisioningState, newState);
+ final DevicePolicyData policyData = getUserData(userHandle);
+ if (transitionCheckNeeded) {
+ // Optional state transition check for non-ADB case.
+ checkUserProvisioningStateTransition(policyData.mUserProvisioningState,
+ newState);
+ }
+ policyData.mUserProvisioningState = newState;
+ saveSettingsLocked(userHandle);
}
- policyData.mUserProvisioningState = newState;
- saveSettingsLocked(userHandle);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
index bf4eeae..8561651 100644
--- a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
@@ -43,13 +43,13 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.BackupManagerService;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.testing.shadows.ShadowSlog;
@@ -75,7 +75,7 @@
@Mock private UserBackupManagerService mBackupManagerService;
@Mock private TransportManager mTransportManager;
@Mock private OnTaskFinishedListener mListener;
- @Mock private IBackupTransport mTransportBinder;
+ @Mock private BackupTransportClient mTransportClient;
@Mock private IBackupObserver mObserver;
@Mock private AlarmManager mAlarmManager;
@Mock private PendingIntent mRunInitIntent;
@@ -101,19 +101,19 @@
@Test
public void testRun_callsTransportCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransportBinder).initializeDevice();
- verify(mTransportBinder).finishBackup();
+ verify(mTransportClient).initializeDevice();
+ verify(mTransportClient).finishBackup();
}
@Test
public void testRun_callsBackupManagerCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -127,7 +127,7 @@
@Test
public void testRun_callsObserverAndListenerCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -140,13 +140,13 @@
@Test
public void testRun_whenInitializeDeviceFails() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransportBinder).initializeDevice();
- verify(mTransportBinder, never()).finishBackup();
+ verify(mTransportClient).initializeDevice();
+ verify(mTransportClient, never()).finishBackup();
verify(mBackupManagerService)
.recordInitPending(true, mTransportName, mTransport.transportDirName);
}
@@ -155,7 +155,7 @@
public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -168,7 +168,7 @@
@Test
public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -179,13 +179,13 @@
@Test
public void testRun_whenFinishBackupFails() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransportBinder).initializeDevice();
- verify(mTransportBinder).finishBackup();
+ verify(mTransportClient).initializeDevice();
+ verify(mTransportClient).finishBackup();
verify(mBackupManagerService)
.recordInitPending(true, mTransportName, mTransport.transportDirName);
}
@@ -193,7 +193,7 @@
@Test
public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -206,7 +206,7 @@
@Test
public void testRun_whenFinishBackupFails_logs() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -219,7 +219,7 @@
@Test
public void testRun_whenInitializeDeviceFails_logs() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -232,7 +232,7 @@
@Test
public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -327,7 +327,7 @@
List<TransportMock> transportMocks =
setUpTransports(mTransportManager, transport1, transport2);
String registeredTransportName = transport2.transportName;
- IBackupTransport registeredTransport = transportMocks.get(1).transport;
+ BackupTransportClient registeredTransport = transportMocks.get(1).transport;
TransportConnection
registeredTransportConnection = transportMocks.get(1).mTransportConnection;
PerformInitializeTask performInitializeTask =
@@ -357,7 +357,7 @@
@Test
public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
TransportMock transportMock = setUpTransport(mTransport);
- IBackupTransport transport = transportMock.transport;
+ BackupTransportClient transport = transportMock.transport;
TransportConnection transportConnection = transportMock.mTransportConnection;
when(transport.initializeDevice()).thenThrow(DeadObjectException.class);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
@@ -380,7 +380,7 @@
}
private void configureTransport(
- IBackupTransport transportMock, int initializeDeviceStatus, int finishBackupStatus)
+ BackupTransportClient transportMock, int initializeDeviceStatus, int finishBackupStatus)
throws Exception {
when(transportMock.initializeDevice()).thenReturn(initializeDeviceStatus);
when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
@@ -389,7 +389,7 @@
private TransportMock setUpTransport(TransportData transport) throws Exception {
TransportMock transportMock =
TransportTestUtils.setUpTransport(mTransportManager, transport);
- mTransportBinder = transportMock.transport;
+ mTransportClient = transportMock.transport;
return transportMock;
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
index ce44f06..8131ac4 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -34,8 +34,8 @@
import android.content.pm.ServiceInfo;
import android.os.RemoteException;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.transport.TransportNotRegisteredException;
@@ -160,7 +160,7 @@
when(transportConnectionMock.getTransportComponent()).thenReturn(transportComponent);
if (status == TransportStatus.REGISTERED_AVAILABLE) {
// Transport registered and available
- IBackupTransport transportMock = mockTransportBinder(transport);
+ BackupTransportClient transportMock = mockTransportBinder(transport);
when(transportConnectionMock.connectOrThrow(any())).thenReturn(transportMock);
when(transportConnectionMock.connect(any())).thenReturn(transportMock);
@@ -179,8 +179,9 @@
}
}
- private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception {
- IBackupTransport transportBinder = mock(IBackupTransport.class);
+ private static BackupTransportClient mockTransportBinder(TransportData transport)
+ throws Exception {
+ BackupTransportClient transportBinder = mock(BackupTransportClient.class);
try {
when(transportBinder.name()).thenReturn(transport.transportName);
when(transportBinder.transportDirName()).thenReturn(transport.transportDirName);
@@ -199,12 +200,12 @@
public static class TransportMock {
public final TransportData transportData;
@Nullable public final TransportConnection mTransportConnection;
- @Nullable public final IBackupTransport transport;
+ @Nullable public final BackupTransportClient transport;
private TransportMock(
TransportData transportData,
@Nullable TransportConnection transportConnection,
- @Nullable IBackupTransport transport) {
+ @Nullable BackupTransportClient transport) {
this.transportData = transportData;
this.mTransportConnection = transportConnection;
this.transport = transport;
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
index de4aec6..6a82f16 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
@@ -84,9 +84,11 @@
@Mock private TransportConnectionListener mTransportConnectionListener;
@Mock private TransportConnectionListener mTransportConnectionListener2;
@Mock private IBackupTransport.Stub mTransportBinder;
+
@UserIdInt private int mUserId;
private TransportStats mTransportStats;
private TransportConnection mTransportConnection;
+ private BackupTransportClient mTransportClient;
private ComponentName mTransportComponent;
private String mTransportString;
private Intent mBindIntent;
@@ -116,6 +118,7 @@
"1",
"caller",
new Handler(mainLooper));
+ mTransportClient = new BackupTransportClient(mTransportBinder);
when(mContext.bindServiceAsUser(
eq(mBindIntent),
@@ -156,7 +159,8 @@
mShadowMainLooper.runToEndOfTasks();
verify(mTransportConnectionListener)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
}
@Test
@@ -169,9 +173,11 @@
connection.onServiceConnected(mTransportComponent, mTransportBinder);
mShadowMainLooper.runToEndOfTasks();
verify(mTransportConnectionListener)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
verify(mTransportConnectionListener2)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
}
@Test
@@ -184,7 +190,8 @@
mShadowMainLooper.runToEndOfTasks();
verify(mTransportConnectionListener2)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
}
@Test
@@ -312,10 +319,10 @@
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
connection.onServiceConnected(mTransportComponent, mTransportBinder);
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> mTransportConnection.connect("caller2"));
- assertThat(transportBinder).isNotNull();
+ assertThat(transportClient).isNotNull();
}
@Test
@@ -325,10 +332,10 @@
connection.onServiceConnected(mTransportComponent, mTransportBinder);
connection.onServiceDisconnected(mTransportComponent);
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> mTransportConnection.connect("caller2"));
- assertThat(transportBinder).isNull();
+ assertThat(transportClient).isNull();
}
@Test
@@ -337,10 +344,10 @@
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
connection.onBindingDied(mTransportComponent);
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> mTransportConnection.connect("caller2"));
- assertThat(transportBinder).isNull();
+ assertThat(transportClient).isNull();
}
@Test
@@ -354,17 +361,17 @@
doAnswer(
invocation -> {
TransportConnectionListener listener = invocation.getArgument(0);
- listener.onTransportConnectionResult(mTransportBinder,
+ listener.onTransportConnectionResult(mTransportClient,
transportConnection);
return null;
})
.when(transportConnection)
.connectAsync(any(), any());
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> transportConnection.connect("caller"));
- assertThat(transportBinder).isNotNull();
+ assertThat(transportClient).isNotNull();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index aa7d6aa..c36e1a8 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -31,13 +31,13 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import com.android.internal.backup.IBackupTransport;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.params.BackupParams;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -57,9 +57,8 @@
@Mock IBackupManagerMonitor mBackupManagerMonitor;
@Mock IBackupObserver mBackupObserver;
@Mock PackageManager mPackageManager;
- @Mock
- TransportConnection mTransportConnection;
- @Mock IBackupTransport mBackupTransport;
+ @Mock TransportConnection mTransportConnection;
+ @Mock BackupTransportClient mBackupTransport;
@Mock BackupEligibilityRules mBackupEligibilityRules;
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java b/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
new file mode 100644
index 0000000..948accc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
+
+import com.google.android.collect.Sets;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LifecycleOperationStorageTest {
+ private static final int USER_ID = 0;
+ private static final int TOKEN_1 = 1;
+ private static final int TOKEN_2 = 2;
+ private static final int TOKEN_3 = 3;
+ private static final long RESULT = 123L;
+
+ private static final String PKG_FOO = "com.android.foo";
+ private static final String PKG_BAR = "com.android.bar";
+ private static final String PKG_BAZ = "com.android.baz";
+ private static final Set<String> MULTIPLE_PKG = Sets.newHashSet(PKG_FOO);
+ private static final Set<String> MULTIPLE_PKGS_1 = Sets.newHashSet(PKG_FOO, PKG_BAR);
+ private static final Set<String> MULTIPLE_PKGS_2 = Sets.newHashSet(PKG_BAR, PKG_BAZ);
+
+ @Mock private BackupRestoreTask mCallback;
+ private LifecycleOperationStorage mOpStorage;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(/* testClass */ this);
+ mOpStorage = new LifecycleOperationStorage(USER_ID);
+ }
+
+ @After
+ public void tearDown() {}
+
+ @Test
+ public void testRegisterOperation_singleOperation() throws Exception {
+ mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+
+ Set<Integer> tokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
+ assertThat(tokens).isEqualTo(only(TOKEN_1));
+ }
+
+ @Test
+ public void testRegisterOperation_multipleOperations() throws Exception {
+ mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+ mOpStorage.registerOperation(TOKEN_2, OpState.ACKNOWLEDGED, mCallback, OpType.BACKUP_WAIT);
+
+ Set<Integer> typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+ Set<Integer> statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+ Set<Integer> stateAcknowledgedTokens =
+ mOpStorage.operationTokensForOpState(OpState.ACKNOWLEDGED);
+
+ assertThat(mOpStorage.numOperations()).isEqualTo(2);
+ assertThat(typeWaitTokens).isEqualTo(Sets.newHashSet(TOKEN_1, TOKEN_2));
+ assertThat(statePendingTokens).isEqualTo(only(TOKEN_1));
+ assertThat(stateAcknowledgedTokens).isEqualTo(only(TOKEN_2));
+ }
+
+ @Test
+ public void testRegisterOperationForPackages_singlePackage() throws Exception {
+ mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING,
+ MULTIPLE_PKG, mCallback, OpType.BACKUP_WAIT);
+
+ Set<Integer> tokens = mOpStorage.operationTokensForPackage(PKG_FOO);
+
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
+ assertThat(tokens).isEqualTo(only(TOKEN_1));
+ }
+
+ @Test
+ public void testRegisterOperationForPackages_multiplePackage() throws Exception {
+ mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING,
+ MULTIPLE_PKGS_1, mCallback, OpType.BACKUP);
+ mOpStorage.registerOperationForPackages(TOKEN_2, OpState.PENDING,
+ MULTIPLE_PKGS_2, mCallback, OpType.BACKUP);
+
+ Set<Integer> tokensFoo = mOpStorage.operationTokensForPackage(PKG_FOO);
+ Set<Integer> tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR);
+ Set<Integer> tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ);
+
+ assertThat(mOpStorage.numOperations()).isEqualTo(2);
+ assertThat(tokensFoo).isEqualTo(only(TOKEN_1));
+ assertThat(tokensBar).isEqualTo(Sets.newHashSet(TOKEN_1, TOKEN_2));
+ assertThat(tokensBaz).isEqualTo(only(TOKEN_2));
+ }
+
+ @Test
+ public void testRemoveOperation() throws Exception {
+ mOpStorage.registerOperation(TOKEN_2, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+
+ Set<Integer> typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+ Set<Integer> statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
+ assertThat(typeWaitTokens).isEqualTo(only(TOKEN_2));
+ assertThat(statePendingTokens).isEqualTo(only(TOKEN_2));
+
+ mOpStorage.removeOperation(TOKEN_2);
+
+ typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+ statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+
+ assertThat(mOpStorage.numOperations()).isEqualTo(0);
+ assertThat(typeWaitTokens).isEmpty();
+ assertThat(statePendingTokens).isEmpty();
+ }
+
+ @Test
+ public void testRemoveOperation_removesPackageMappings() throws Exception {
+ mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING, MULTIPLE_PKGS_1,
+ mCallback, OpType.BACKUP);
+ mOpStorage.registerOperationForPackages(TOKEN_2, OpState.PENDING, MULTIPLE_PKGS_2,
+ mCallback, OpType.BACKUP);
+
+ mOpStorage.removeOperation(TOKEN_2);
+
+ Set<Integer> tokensFoo = mOpStorage.operationTokensForPackage(PKG_FOO);
+ Set<Integer> tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR);
+ Set<Integer> tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ);
+
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
+ assertThat(tokensFoo).isEqualTo(only(TOKEN_1));
+ assertThat(tokensBar).isEqualTo(only(TOKEN_1));
+ assertThat(tokensBaz).isEmpty();
+ }
+
+ @Test
+ public void testIsBackupOperationInProgress() throws Exception {
+ mOpStorage.registerOperation(TOKEN_1, OpState.ACKNOWLEDGED, mCallback, OpType.RESTORE_WAIT);
+ assertThat(mOpStorage.isBackupOperationInProgress()).isFalse();
+
+ mOpStorage.registerOperation(TOKEN_2, OpState.TIMEOUT, mCallback, OpType.BACKUP_WAIT);
+ assertThat(mOpStorage.isBackupOperationInProgress()).isFalse();
+
+ mOpStorage.registerOperation(TOKEN_3, OpState.PENDING, mCallback, OpType.BACKUP);
+ assertThat(mOpStorage.isBackupOperationInProgress()).isTrue();
+ }
+
+ @Test
+ public void testOnOperationComplete_pendingAdvancesState_invokesCallback() throws Exception {
+ mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+
+ mOpStorage.onOperationComplete(TOKEN_1, RESULT, callback -> {
+ mCallback.operationComplete(RESULT);
+ });
+
+ assertThat(mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT))
+ .isEqualTo(only(TOKEN_1));
+ assertThat(mOpStorage.operationTokensForOpState(OpState.PENDING)).isEmpty();
+ assertThat(mOpStorage.operationTokensForOpState(OpState.ACKNOWLEDGED)).isNotEmpty();
+ verify(mCallback).operationComplete(RESULT);
+ }
+
+ private Set<Integer> only(Integer val) {
+ return Sets.newHashSet(val);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index b3f7587..b255a35 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -302,6 +302,65 @@
testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
}
+ // TODO (b/208484275) : Enable these tests
+ // @Test
+ // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(false);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
+ // @Test
+ // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(true);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED,
+ // preAuthInfo.getCanAuthenticateResult());
+ // // Even though canAuth returns privacy enabled, we should still be able to authenticate.
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
+ // @Test
+ // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(true);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo =
+ // createPromptInfo(Authenticators.BIOMETRIC_STRONG
+ // | Authenticators. DEVICE_CREDENTIAL);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException {
final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
@@ -331,7 +390,8 @@
userId,
promptInfo,
TEST_PACKAGE,
- checkDevicePolicyManager);
+ checkDevicePolicyManager,
+ mContext);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java b/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java
new file mode 100644
index 0000000..f172279
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * {@link ContextWrapper} that doesn't register {@link BroadcastReceiver}.
+ *
+ * Instead, it keeps a list of the registrations for querying.
+ */
+class NoBroadcastContextWrapper extends TestableContext {
+
+ ArrayList<BroadcastReceiverRegistration> mRegistrationList =
+ new ArrayList<>();
+
+ NoBroadcastContextWrapper(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
+ return registerReceiver(receiver, filter, 0);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ int flags) {
+ return registerReceiver(receiver, filter, null, null, flags);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ @Nullable String broadcastPermission, @Nullable Handler scheduler) {
+ return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ @Nullable String broadcastPermission, @Nullable Handler scheduler, int flags) {
+ return registerReceiverAsUser(receiver, getUser(), filter, broadcastPermission, scheduler,
+ flags);
+ }
+
+ @Nullable
+ @Override
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ return registerReceiverForAllUsers(receiver, filter, broadcastPermission, scheduler, 0);
+ }
+
+ @Nullable
+ @Override
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler, int flags) {
+ return registerReceiverAsUser(receiver, UserHandle.ALL, filter, broadcastPermission,
+ scheduler, flags);
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(@Nullable BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ return registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler, 0);
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(@Nullable BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler, int flags) {
+ BroadcastReceiverRegistration reg = new BroadcastReceiverRegistration(
+ receiver, user, filter, broadcastPermission, scheduler, flags
+ );
+ mRegistrationList.add(reg);
+ return null;
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mRegistrationList.removeIf((reg) -> reg.mReceiver == receiver);
+ }
+
+ static class BroadcastReceiverRegistration {
+ final BroadcastReceiver mReceiver;
+ final UserHandle mUser;
+ final IntentFilter mIntentFilter;
+ final String mBroadcastPermission;
+ final Handler mHandler;
+ final int mFlags;
+
+ BroadcastReceiverRegistration(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter intentFilter, String broadcastPermission, Handler handler, int flags) {
+ mReceiver = receiver;
+ mUser = user;
+ mIntentFilter = intentFilter;
+ mBroadcastPermission = broadcastPermission;
+ mHandler = handler;
+ mFlags = flags;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
new file mode 100644
index 0000000..c293b5e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.statusbar;
+
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.quicksettings.TileService;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.IStatusBar;
+import com.android.server.LocalServices;
+import com.android.server.policy.GlobalActionsProvider;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class StatusBarManagerServiceTest {
+
+ private static final String TEST_PACKAGE = "test_pkg";
+ private static final String TEST_SERVICE = "test_svc";
+ private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+ TEST_SERVICE);
+ private static final CharSequence APP_NAME = "AppName";
+ private static final CharSequence TILE_LABEL = "Tile label";
+
+ @Rule
+ public final TestableContext mContext =
+ new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
+
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternal;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+ @Mock
+ private IStatusBar.Stub mMockStatusBar;
+ @Captor
+ private ArgumentCaptor<IAddTileResultCallback> mAddTileResultCallbackCaptor;
+
+ private Icon mIcon;
+ private StatusBarManagerService mStatusBarManagerService;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+
+ when(mMockStatusBar.asBinder()).thenReturn(mMockStatusBar);
+ when(mApplicationInfo.loadLabel(any())).thenReturn(APP_NAME);
+
+ mStatusBarManagerService = new StatusBarManagerService(mContext);
+ LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.removeServiceForTest(GlobalActionsProvider.class);
+
+ mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(
+ mStatusBarManagerService);
+
+ mStatusBarManagerService.registerStatusBar(mMockStatusBar);
+
+ mIcon = Icon.createWithResource(mContext, android.R.drawable.btn_plus);
+ }
+
+ @Test
+ public void testHandleIncomingUserCalled() {
+ int fakeUser = 17;
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ fakeUser,
+ new Callback()
+ );
+ fail("Should have SecurityException from uid check");
+ } catch (SecurityException e) {
+ verify(mActivityManagerInternal).handleIncomingUser(
+ eq(Binder.getCallingPid()),
+ eq(Binder.getCallingUid()),
+ eq(fakeUser),
+ eq(false),
+ eq(ActivityManagerInternal.ALLOW_NON_FULL),
+ anyString(),
+ eq(TEST_PACKAGE)
+ );
+ }
+ }
+
+ @Test
+ public void testCheckUid_pass() {
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+ .thenReturn(Binder.getCallingUid());
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ mContext.getUserId(),
+ new Callback()
+ );
+ } catch (SecurityException e) {
+ fail("No SecurityException should be thrown");
+ }
+ }
+
+ @Test
+ public void testCheckUid_pass_differentUser() {
+ int otherUserUid = UserHandle.getUid(17, UserHandle.getAppId(Binder.getCallingUid()));
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+ .thenReturn(otherUserUid);
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ mContext.getUserId(),
+ new Callback()
+ );
+ } catch (SecurityException e) {
+ fail("No SecurityException should be thrown");
+ }
+ }
+
+ @Test
+ public void testCheckUid_fail() {
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+ .thenReturn(Binder.getCallingUid() + 1);
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ mContext.getUserId(),
+ new Callback()
+ );
+ fail("Should throw SecurityException");
+ } catch (SecurityException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testCurrentUser_fail() {
+ mockUidCheck();
+ int user = 0;
+ when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user + 1);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testCurrentUser_pass() {
+ mockUidCheck();
+ int user = 0;
+ when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_noComponentFound() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(null);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_notEnabled() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_noPermission() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_notExported() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+ r.serviceInfo.exported = false;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_pass() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+ r.serviceInfo.exported = true;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testAppInForeground_fail() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ mockComponentInfo(user);
+
+ when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+ PROCESS_STATE_FOREGROUND_SERVICE);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testAppInForeground_pass() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ mockComponentInfo(user);
+
+ when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+ PROCESS_STATE_TOP);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testRequestToStatusBar() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ any()
+ );
+ }
+
+ @Test
+ public void testRequestInProgress_samePackage() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testRequestInProgress_differentPackage() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+ ComponentName otherComponent = new ComponentName("a", "b");
+ mockUidCheck(otherComponent.getPackageName());
+ mockComponentInfo(user, otherComponent);
+
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(otherComponent, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_tileAdded() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED);
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED, callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_tileNotAdded() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_tileAlreadyAdded() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED);
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_dialogDismissed() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
+ // This gets translated to TILE_NOT_ADDED
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testInstaDenialAfterManyDenials() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+ }
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ // Only called MAX_NUM_DENIALS times
+ verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
+ any(),
+ any(),
+ any(),
+ any(),
+ mAddTileResultCallbackCaptor.capture()
+ );
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testDialogDismissalNotCountingAgainstDenials() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS * 2; i++) {
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
+ }
+ }
+
+ private void mockUidCheck() {
+ mockUidCheck(TEST_PACKAGE);
+ }
+
+ private void mockUidCheck(String packageName) {
+ when(mPackageManagerInternal.getPackageUid(eq(packageName), anyInt(), anyInt()))
+ .thenReturn(Binder.getCallingUid());
+ }
+
+ private void mockCurrentUserCheck(int user) {
+ when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
+ }
+
+ private void mockComponentInfo(int user) {
+ mockComponentInfo(user, TEST_COMPONENT);
+ }
+
+ private ResolveInfo makeResolveInfo() {
+ ResolveInfo r = new ResolveInfo();
+ r.serviceInfo = new ServiceInfo();
+ r.serviceInfo.applicationInfo = mApplicationInfo;
+ return r;
+ }
+
+ private void mockComponentInfo(int user, ComponentName componentName) {
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.exported = true;
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(componentName));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(componentName,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ }
+
+ private void mockProcessState() {
+ when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+ PROCESS_STATE_TOP);
+ }
+
+ private void mockEverything(int user) {
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ mockComponentInfo(user);
+ mockProcessState();
+ }
+
+ private static class Callback extends IAddTileResultCallback.Stub {
+ int mUserResponse = -1;
+
+ @Override
+ public void onTileRequest(int userResponse) throws RemoteException {
+ if (mUserResponse != -1) {
+ throw new IllegalStateException(
+ "Setting response to " + userResponse + " but it already has "
+ + mUserResponse);
+ }
+ mUserResponse = userResponse;
+ }
+ }
+
+ private static class IntentMatcher implements ArgumentMatcher<Intent> {
+ private final Intent mIntent;
+
+ IntentMatcher(Intent intent) {
+ mIntent = intent;
+ }
+
+ @Override
+ public boolean matches(Intent argument) {
+ return argument != null && argument.filterEquals(mIntent);
+ }
+
+ @Override
+ public String toString() {
+ return "Expected: " + mIntent;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java b/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java
new file mode 100644
index 0000000..dac6df9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.statusbar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class TileRequestTrackerTest {
+
+ private static final String TEST_PACKAGE = "test_pkg";
+ private static final String TEST_SERVICE = "test_svc";
+ private static final String TEST_SERVICE_OTHER = "test_svc_other";
+ private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+ TEST_SERVICE);
+ private static final ComponentName TEST_COMPONENT_OTHER = new ComponentName(TEST_PACKAGE,
+ TEST_SERVICE_OTHER);
+ private static final ComponentName TEST_COMPONENT_OTHER_PACKAGE = new ComponentName("other",
+ TEST_SERVICE);
+ private static final int USER_ID = 0;
+ private static final int USER_ID_OTHER = 10;
+ private static final int APP_UID = 12345;
+ private static final int USER_UID = UserHandle.getUid(USER_ID, APP_UID);
+ private static final int USER_OTHER_UID = UserHandle.getUid(USER_ID_OTHER, APP_UID);
+
+ @Rule
+ public final NoBroadcastContextWrapper mContext =
+ new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
+
+ private TileRequestTracker mTileRequestTracker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTileRequestTracker = new TileRequestTracker(mContext);
+ }
+
+ @Test
+ public void testBroadcastReceiverRegistered() {
+ NoBroadcastContextWrapper.BroadcastReceiverRegistration reg = getReceiverRegistration();
+
+ assertEquals(UserHandle.ALL, reg.mUser);
+ assertNull(reg.mBroadcastPermission);
+ assertNotNull(reg.mReceiver);
+
+ IntentFilter filter = reg.mIntentFilter;
+ assertEquals(2, filter.countActions());
+ assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED));
+ assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_DATA_CLEARED));
+ assertTrue(filter.hasDataScheme("package"));
+ }
+
+ @Test
+ public void testNoDenialsFromStart() {
+ // Certainly not an exhaustive test
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT_OTHER));
+ }
+
+ @Test
+ public void testNoDenialBeforeMax() {
+ for (int i = 1; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testDenialOnMax() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testDenialPerUser() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testDenialPerComponent() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+ }
+
+ @Test
+ public void testPackageUninstallRemovesDenials_allComponents() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT_OTHER);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+ }
+
+ @Test
+ public void testPackageUninstallRemoveDenials_differentUsers() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ mTileRequestTracker.addDenial(USER_ID_OTHER, TEST_COMPONENT);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_UID, USER_OTHER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ // User 0 package was not removed
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ // User 10 package was removed
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testPackageUninstallRemoveDenials_differentPackages() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT_OTHER_PACKAGE);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ // Package TEST_PACKAGE removed
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ // Package "other" not removed
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER_PACKAGE));
+ }
+
+ @Test
+ public void testPackageUpdateDoesntRemoveDenials() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_REPLACING, true);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testClearPackageDataRemovesDenials() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ private NoBroadcastContextWrapper.BroadcastReceiverRegistration getReceiverRegistration() {
+ assertEquals(1, mContext.mRegistrationList.size());
+ return mContext.mRegistrationList.get(0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 4dcd633..add4cda 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -62,12 +62,15 @@
private static final String LOG_TAG = "SystemConfigTest";
private SystemConfig mSysConfig;
+ private File mFooJar;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@Before
public void setUp() throws Exception {
mSysConfig = new SystemConfigTestClass();
+ mFooJar = createTempFile(
+ mTemporaryFolder.getRoot().getCanonicalFile(), "foo.jar", "JAR");
}
/**
@@ -340,7 +343,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " on-bootclasspath-before=\"10\"\n"
+ " on-bootclasspath-since=\"20\"\n"
+ " />\n\n"
@@ -362,7 +365,7 @@
"<permissions>\n"
+ " <updatable-library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " on-bootclasspath-before=\"10\"\n"
+ " on-bootclasspath-since=\"20\"\n"
+ " />\n\n"
@@ -384,7 +387,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " min-device-sdk=\"30\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -402,7 +405,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " min-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -420,7 +423,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " min-device-sdk=\"" + (Build.VERSION.SDK_INT + 1) + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -438,7 +441,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " max-device-sdk=\"30\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -456,7 +459,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " max-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -474,7 +477,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " max-device-sdk=\"" + (Build.VERSION.SDK_INT + 1) + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -507,7 +510,7 @@
* @param folder pre-existing subdirectory of mTemporaryFolder to put the file
* @param fileName name of the file (e.g. filename.xml) to create
* @param contents contents to write to the file
- * @return the folder containing the newly created file (not the file itself!)
+ * @return the newly created file
*/
private File createTempFile(File folder, String fileName, String contents)
throws IOException {
@@ -523,13 +526,13 @@
Log.d(LOG_TAG, input.nextLine());
}
- return folder;
+ return file;
}
private void assertFooIsOnlySharedLibrary() {
assertThat(mSysConfig.getSharedLibraries().size()).isEqualTo(1);
SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
assertThat(entry.name).isEqualTo("foo");
- assertThat(entry.filename).isEqualTo("foo.jar");
+ assertThat(entry.filename).isEqualTo(mFooJar.toString());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index c0959d3..2ea7fda 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -22,6 +22,9 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -92,6 +95,7 @@
final ActivityRecord activity = createActivityRecord(dc);
mDc.prepareAppTransition(TRANSIT_OPEN);
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
mDc.mOpeningApps.add(activity);
assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
@@ -102,6 +106,22 @@
}
@Test
+ public void testKeyguardUnoccludeOcclude() {
+ final DisplayContent dc = createNewDisplay(Display.STATE_ON);
+ final ActivityRecord activity = createActivityRecord(dc);
+
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
+ mDc.mOpeningApps.add(activity);
+ assertEquals(TRANSIT_NONE,
+ AppTransitionController.getTransitCompatType(mDc.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
+
+ }
+
+ @Test
public void testKeyguardKeep() {
final DisplayContent dc = createNewDisplay(Display.STATE_ON);
final ActivityRecord activity = createActivityRecord(dc);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5f21e87..0267b68 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -117,6 +117,7 @@
import com.android.internal.telephony.ISub;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.IUpdateAvailableNetworksCallback;
+import com.android.internal.telephony.IccLogicalChannelRequest;
import com.android.internal.telephony.OperatorInfo;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
@@ -6774,8 +6775,13 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.iccOpenLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
- getOpPackageName(), aid, p2);
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.slotIndex = slotIndex;
+ request.aid = aid;
+ request.p2 = p2;
+ request.callingPackage = getOpPackageName();
+ request.binder = new Binder();
+ return telephony.iccOpenLogicalChannel(request);
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
@@ -6842,8 +6848,15 @@
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.iccOpenLogicalChannel(subId, getOpPackageName(), AID, p2);
+ if (telephony != null) {
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.subId = subId;
+ request.callingPackage = getOpPackageName();
+ request.aid = AID;
+ request.p2 = p2;
+ request.binder = new Binder();
+ return telephony.iccOpenLogicalChannel(request);
+ }
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
}
@@ -6873,8 +6886,10 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.iccCloseLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
- channel);
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.slotIndex = slotIndex;
+ request.channel = channel;
+ return telephony.iccCloseLogicalChannel(request);
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
@@ -6917,8 +6932,12 @@
public boolean iccCloseLogicalChannel(int subId, int channel) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.iccCloseLogicalChannel(subId, channel);
+ if (telephony != null) {
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.subId = subId;
+ request.channel = channel;
+ return telephony.iccCloseLogicalChannel(request);
+ }
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 00b57e6..d5c9ec4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,6 +67,7 @@
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
import com.android.internal.telephony.ICallForwardingInfoCallback;
+import com.android.internal.telephony.IccLogicalChannelRequest;
import com.android.internal.telephony.IImsStateCallback;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.INumberVerificationCallback;
@@ -584,59 +585,24 @@
void setCellInfoListRate(int rateInMillis);
/**
- * Opens a logical channel to the ICC card using the physical slot index and port index.
- *
- * Input parameters equivalent to TS 27.007 AT+CCHO command.
- *
- * @param slotIndex The physical slot index of the target ICC card
- * @param portIndex The unique index referring to a port belonging to the SIM slot
- * @param callingPackage the name of the package making the call.
- * @param AID Application id. See ETSI 102.221 and 101.220.
- * @param p2 P2 parameter (described in ISO 7816-4).
- * @return an IccOpenLogicalChannelResponse object.
- */
- IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(
- int slotIndex, int portIndex, String callingPackage, String AID, int p2);
-
- /**
* Opens a logical channel to the ICC card.
*
* Input parameters equivalent to TS 27.007 AT+CCHO command.
*
- * @param subId The subscription to use.
- * @param callingPackage the name of the package making the call.
- * @param AID Application id. See ETSI 102.221 and 101.220.
- * @param p2 P2 parameter (described in ISO 7816-4).
+ * @param request the parcelable used to indicate how to open the logical channel.
* @return an IccOpenLogicalChannelResponse object.
*/
- IccOpenLogicalChannelResponse iccOpenLogicalChannel(
- int subId, String callingPackage, String AID, int p2);
-
- /**
- * Closes a previously opened logical channel to the ICC card using the physical slot index and port index.
- *
- * Input parameters equivalent to TS 27.007 AT+CCHC command.
- *
- * @param slotIndex The physical slot index of the target ICC card
- * @param portIndex The unique index referring to a port belonging to the SIM slot
- * @param channel is the channel id to be closed as returned by a
- * successful iccOpenLogicalChannel.
- * @return true if the channel was closed successfully.
- */
- boolean iccCloseLogicalChannelByPort(int slotIndex, int portIndex, int channel);
+ IccOpenLogicalChannelResponse iccOpenLogicalChannel(in IccLogicalChannelRequest request);
/**
* Closes a previously opened logical channel to the ICC card.
*
* Input parameters equivalent to TS 27.007 AT+CCHC command.
*
- * @param subId The subscription to use.
- * @param channel is the channel id to be closed as returned by a
- * successful iccOpenLogicalChannel.
+ * @param request the parcelable used to indicate how to close the logical channel.
* @return true if the channel was closed successfully.
*/
- @UnsupportedAppUsage(trackingBug = 171933273)
- boolean iccCloseLogicalChannel(int subId, int channel);
+ boolean iccCloseLogicalChannel(in IccLogicalChannelRequest request);
/**
* Transmit an APDU to the ICC card over a logical channel using the physical slot index and port index.
diff --git a/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl b/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl
new file mode 100644
index 0000000..a84e752
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl
@@ -0,0 +1,52 @@
+/*
+** Copyright 2007, 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.telephony;
+
+import android.os.IBinder;
+
+/**
+ * A request to open or close a logical channel to the ICC card.
+ *
+ * @hide
+ */
+@JavaDerive(toString=true, equals=true)
+parcelable IccLogicalChannelRequest {
+
+ /** Subscription id. */
+ int subId = -1;
+
+ /** Physical slot index of the ICC card. */
+ int slotIndex = -1;
+
+ /** The unique index referring to a port belonging to the ICC card slot. */
+ int portIndex = 0;
+
+ /** Package name for the calling app, used only when open channel. */
+ @nullable String callingPackage;
+
+ /** Application id, used only when open channel. */
+ @nullable String aid;
+
+ /** The P2 parameter described in ISO 7816-4, used only when open channel. */
+ int p2 = 0;
+
+ /** Channel number */
+ int channel = -1;
+
+ /** A IBinder object for server side to check if the request client is still living. */
+ @nullable IBinder binder;
+}