Merge "[res] AliasMap - use sorted vector instead of map"
diff --git a/core/api/current.txt b/core/api/current.txt
index 7cfb7f4..a03a753 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1041,6 +1041,7 @@
field public static final int max = 16843062; // 0x1010136
field public static final int maxAspectRatio = 16844128; // 0x1010560
field public static final int maxButtonHeight = 16844029; // 0x10104fd
+ field public static final int maxConcurrentSessionsCount;
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
field public static final int maxHeight = 16843040; // 0x1010120
@@ -11663,6 +11664,7 @@
public class PackageInstaller {
method public void abandonSession(int);
+ method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -11707,6 +11709,35 @@
field public static final int STATUS_SUCCESS = 0; // 0x0
}
+ public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isRequireAppNotForeground();
+ method public boolean isRequireAppNotInteracting();
+ method public boolean isRequireAppNotTopVisible();
+ method public boolean isRequireDeviceIdle();
+ method public boolean isRequireNotInCall();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
+ field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
+ }
+
+ public static final class PackageInstaller.InstallConstraints.Builder {
+ ctor public PackageInstaller.InstallConstraints.Builder();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
+ }
+
+ public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isAllConstraintsSatisfied();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
+ }
+
public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.graphics.Bitmap getIcon();
@@ -42010,6 +42041,7 @@
field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+ field public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL = "include_lte_for_nr_advanced_threshold_bandwidth_bool";
field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool";
field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 057c1ada..c10504d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2964,6 +2964,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
+ method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -2981,6 +2982,7 @@
method public int getLockState();
method @Nullable public String getName();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
+ method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
@@ -2997,6 +2999,7 @@
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
@@ -3054,6 +3057,50 @@
}
+package android.companion.virtual.sensor {
+
+ public class VirtualSensor {
+ method @NonNull public String getName();
+ method public int getType();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+ }
+
+ public static interface VirtualSensor.SensorStateChangeCallback {
+ method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+ }
+
+ public final class VirtualSensorConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getName();
+ method public int getType();
+ method @Nullable public String getVendor();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
+ }
+
+ public static final class VirtualSensorConfig.Builder {
+ ctor public VirtualSensorConfig.Builder(int, @NonNull String);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
+ }
+
+ public final class VirtualSensorEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getTimestampNanos();
+ method @NonNull public float[] getValues();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR;
+ }
+
+ public static final class VirtualSensorEvent.Builder {
+ ctor public VirtualSensorEvent.Builder(@NonNull float[]);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build();
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long);
+ }
+
+}
+
package android.content {
public class ApexEnvironment {
@@ -10049,6 +10096,7 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.NewUserResponse createUser(@NonNull android.os.NewUserRequest);
method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 35e01f1..fa73178 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1251,6 +1251,7 @@
field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3
field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
+ field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000
field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
}
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 4d4a4d7..e447d86 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -402,7 +402,7 @@
mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
mCallingUid);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+ startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
return;
}
} catch (OperationCanceledException e) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f25e639..9d5c01a 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -767,11 +767,11 @@
*/
@SystemApi
public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
- Preconditions.checkArgument(!namespace.contains("/"),
- "namespace should not contain '/'");
- Preconditions.checkArgument(!key.contains("/"),
- "key should not contain '/'");
- mDeliveryGroupMatchingKey = namespace + "/" + key;
+ Preconditions.checkArgument(!namespace.contains(":"),
+ "namespace should not contain ':'");
+ Preconditions.checkArgument(!key.contains(":"),
+ "key should not contain ':'");
+ mDeliveryGroupMatchingKey = namespace + ":" + key;
}
/**
@@ -779,7 +779,7 @@
* broadcast belongs to.
*
* @return the delivery group namespace and key that was previously set using
- * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code /}.
+ * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}.
* @hide
*/
@SystemApi
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 9bf8550..63fdc2e 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -48,6 +48,7 @@
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.ServiceInfo.ForegroundServiceType;
@@ -879,7 +880,8 @@
int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
int callerPid, String packageName, boolean allowWhileInUse) {
// Simple case, check if it's already granted.
- if (context.checkPermission(name, callerPid, callerUid) == PERMISSION_GRANTED) {
+ if (PermissionChecker.checkPermissionForPreflight(context, name,
+ callerPid, callerUid, packageName) == PERMISSION_GRANTED) {
return PERMISSION_GRANTED;
}
if (allowWhileInUse) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 295d69d..0837d85 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -19,6 +19,9 @@
import android.app.PendingIntent;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.input.VirtualKeyEvent;
@@ -97,6 +100,24 @@
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
+ * Creates a virtual sensor, capable of injecting sensor events into the system.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+ void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
+
+ /**
+ * Removes the sensor corresponding to the given token from the system.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+ void unregisterSensor(IBinder token);
+
+ /**
+ * Sends an event to the virtual sensor corresponding to the given token.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+ boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
+
+ /**
* Launches a pending intent on the given display that is owned by this virtual device.
*/
void launchPendingIntent(
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 9154701..01b42bf 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -22,12 +22,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Point;
@@ -58,6 +61,7 @@
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -76,7 +80,8 @@
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
| DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
| DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
/**
* The default device ID, which is the ID of the primary (non-virtual) device.
@@ -88,6 +93,26 @@
*/
public static final int INVALID_DEVICE_ID = -1;
+ /**
+ * Broadcast Action: A Virtual Device was removed.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_VIRTUAL_DEVICE_REMOVED =
+ "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED";
+
+ /**
+ * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}.
+ * Contains the identifier of the virtual device, which was removed.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VIRTUAL_DEVICE_ID =
+ "android.companion.virtual.extra.VIRTUAL_DEVICE_ID";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
@@ -250,7 +275,10 @@
};
@Nullable
private VirtualAudioDevice mVirtualAudioDevice;
+ @NonNull
+ private List<VirtualSensor> mVirtualSensors = new ArrayList<>();
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private VirtualDevice(
IVirtualDeviceManager service,
Context context,
@@ -264,6 +292,10 @@
associationId,
params,
mActivityListenerBinder);
+ final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
+ for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
+ mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
+ }
}
/**
@@ -278,6 +310,23 @@
}
/**
+ * Returns this device's sensor with the given type and name, if any.
+ *
+ * @see VirtualDeviceParams.Builder#addVirtualSensorConfig
+ *
+ * @param type The type of the sensor.
+ * @param name The name of the sensor.
+ * @return The matching sensor if found, {@code null} otherwise.
+ */
+ @Nullable
+ public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
+ return mVirtualSensors.stream()
+ .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
+ .findAny()
+ .orElse(null);
+ }
+
+ /**
* Launches a given pending intent on the give display ID.
*
* @param displayId The display to launch the pending intent on. This display must be
@@ -437,6 +486,7 @@
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
try {
+ // This also takes care of unregistering all virtual sensors.
mVirtualDevice.close();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -622,6 +672,28 @@
}
/**
+ * Creates a virtual sensor, capable of injecting sensor events into the system. Only for
+ * internal use, since device sensors must remain valid for the entire lifetime of the
+ * device.
+ *
+ * @param config The configuration of the sensor.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
+ Objects.requireNonNull(config);
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.sensor.VirtualSensor:" + config.getName());
+ mVirtualDevice.createVirtualSensor(token, config);
+ return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Adds an activity listener to listen for events such as top activity change or virtual
* display task stack became empty.
*
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index f8c2e34a..bad26c6 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -23,20 +23,22 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.SparseArray;
import android.util.SparseIntArray;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -158,6 +160,7 @@
@Nullable private final String mName;
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
+ @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
private VirtualDeviceParams(
@LockState int lockState,
@@ -169,24 +172,22 @@
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
@Nullable String name,
- @NonNull SparseIntArray devicePolicies) {
- Preconditions.checkNotNull(usersWithMatchingAccounts);
- Preconditions.checkNotNull(allowedCrossTaskNavigations);
- Preconditions.checkNotNull(blockedCrossTaskNavigations);
- Preconditions.checkNotNull(allowedActivities);
- Preconditions.checkNotNull(blockedActivities);
- Preconditions.checkNotNull(devicePolicies);
-
+ @NonNull SparseIntArray devicePolicies,
+ @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
mLockState = lockState;
- mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
- mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
- mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
+ mUsersWithMatchingAccounts =
+ new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
+ mAllowedCrossTaskNavigations =
+ new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations));
+ mBlockedCrossTaskNavigations =
+ new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations));
mDefaultNavigationPolicy = defaultNavigationPolicy;
- mAllowedActivities = new ArraySet<>(allowedActivities);
- mBlockedActivities = new ArraySet<>(blockedActivities);
+ mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities));
+ mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities));
mDefaultActivityPolicy = defaultActivityPolicy;
mName = name;
- mDevicePolicies = devicePolicies;
+ mDevicePolicies = Objects.requireNonNull(devicePolicies);
+ mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
}
@SuppressWarnings("unchecked")
@@ -201,6 +202,8 @@
mDefaultActivityPolicy = parcel.readInt();
mName = parcel.readString8();
mDevicePolicies = parcel.readSparseIntArray();
+ mVirtualSensorConfigs = new ArrayList<>();
+ parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
}
/**
@@ -316,6 +319,15 @@
return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
}
+ /**
+ * Returns the configurations for all sensors that should be created for this device.
+ *
+ * @see Builder#addVirtualSensorConfig
+ */
+ public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() {
+ return mVirtualSensorConfigs;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -333,6 +345,7 @@
dest.writeInt(mDefaultActivityPolicy);
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
+ dest.writeTypedList(mVirtualSensorConfigs);
}
@Override
@@ -428,6 +441,7 @@
private boolean mDefaultActivityPolicyConfigured = false;
@Nullable private String mName;
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
+ @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -467,8 +481,7 @@
@NonNull
public Builder setUsersWithMatchingAccounts(
@NonNull Set<UserHandle> usersWithMatchingAccounts) {
- Preconditions.checkNotNull(usersWithMatchingAccounts);
- mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+ mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts);
return this;
}
@@ -491,7 +504,6 @@
@NonNull
public Builder setAllowedCrossTaskNavigations(
@NonNull Set<ComponentName> allowedCrossTaskNavigations) {
- Preconditions.checkNotNull(allowedCrossTaskNavigations);
if (mDefaultNavigationPolicyConfigured
&& mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) {
throw new IllegalArgumentException(
@@ -500,7 +512,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
mDefaultNavigationPolicyConfigured = true;
- mAllowedCrossTaskNavigations = allowedCrossTaskNavigations;
+ mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations);
return this;
}
@@ -523,7 +535,6 @@
@NonNull
public Builder setBlockedCrossTaskNavigations(
@NonNull Set<ComponentName> blockedCrossTaskNavigations) {
- Preconditions.checkNotNull(blockedCrossTaskNavigations);
if (mDefaultNavigationPolicyConfigured
&& mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) {
throw new IllegalArgumentException(
@@ -532,7 +543,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
mDefaultNavigationPolicyConfigured = true;
- mBlockedCrossTaskNavigations = blockedCrossTaskNavigations;
+ mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations);
return this;
}
@@ -551,7 +562,6 @@
*/
@NonNull
public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
- Preconditions.checkNotNull(allowedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
throw new IllegalArgumentException(
@@ -559,7 +569,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
mDefaultActivityPolicyConfigured = true;
- mAllowedActivities = allowedActivities;
+ mAllowedActivities = Objects.requireNonNull(allowedActivities);
return this;
}
@@ -578,7 +588,6 @@
*/
@NonNull
public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
- Preconditions.checkNotNull(blockedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
throw new IllegalArgumentException(
@@ -586,7 +595,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
mDefaultActivityPolicyConfigured = true;
- mBlockedActivities = blockedActivities;
+ mBlockedActivities = Objects.requireNonNull(blockedActivities);
return this;
}
@@ -621,10 +630,49 @@
}
/**
+ * Adds a configuration for a sensor that should be created for this virtual device.
+ *
+ * Device sensors must remain valid for the entire lifetime of the device, hence they are
+ * created together with the device itself, and removed when the device is removed.
+ *
+ * Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
+ *
+ * @see android.companion.virtual.sensor.VirtualSensor
+ * @see #addDevicePolicy
+ */
+ @NonNull
+ public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
+ mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig));
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
+ *
+ * @throws IllegalArgumentException if there's mismatch between policy definition and
+ * the passed parameters or if there are sensor configs with the same type and name.
+ *
*/
@NonNull
public VirtualDeviceParams build() {
+ if (!mVirtualSensorConfigs.isEmpty()
+ && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
+ != DEVICE_POLICY_CUSTOM)) {
+ throw new IllegalArgumentException(
+ "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+ + "virtual sensors.");
+ }
+ SparseArray<Set<String>> sensorNameByType = new SparseArray();
+ for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
+ VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
+ Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
+ if (!sensorNames.add(config.getName())) {
+ throw new IllegalArgumentException(
+ "Sensor names must be unique for a particular sensor type.");
+ }
+ sensorNameByType.put(config.getType(), sensorNames);
+ }
+
return new VirtualDeviceParams(
mLockState,
mUsersWithMatchingAccounts,
@@ -635,7 +683,8 @@
mBlockedActivities,
mDefaultActivityPolicy,
mName,
- mDevicePolicies);
+ mDevicePolicies,
+ mVirtualSensorConfigs);
}
}
}
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
new file mode 100644
index 0000000..b99cc7e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+/**
+ * Interface for notification of listener registration changes for a virtual sensor.
+ *
+ * @hide
+ */
+oneway interface IVirtualSensorStateChangeCallback {
+
+ /**
+ * Called when the registered listeners to a virtual sensor have changed.
+ *
+ * @param enabled Whether the sensor is enabled.
+ * @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
+ * @param batchReportingLatencyMicros The requested maximum time interval in microseconds
+ * between the delivery of two batches of sensor events.
+ */
+ void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
new file mode 100644
index 0000000..a184481
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.time.Duration;
+
+/**
+ * Representation of a sensor on a remote device, capable of sending events, such as an
+ * accelerometer or a gyroscope.
+ *
+ * This registers the sensor device with the sensor framework as a runtime sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualSensor {
+
+ /**
+ * Interface for notification of listener registration changes for a virtual sensor.
+ */
+ public interface SensorStateChangeCallback {
+ /**
+ * Called when the registered listeners to a virtual sensor have changed.
+ *
+ * @param enabled Whether the sensor is enabled.
+ * @param samplingPeriod The requested sampling period of the sensor.
+ * @param batchReportLatency The requested maximum time interval between the delivery of two
+ * batches of sensor events.
+ */
+ void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod,
+ @NonNull Duration batchReportLatency);
+ }
+
+ private final int mType;
+ private final String mName;
+ private final IVirtualDevice mVirtualDevice;
+ private final IBinder mToken;
+
+ /**
+ * @hide
+ */
+ public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) {
+ mType = type;
+ mName = name;
+ mVirtualDevice = virtualDevice;
+ mToken = token;
+ }
+
+ /**
+ * Returns the
+ * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the sensor.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Send a sensor event to the system.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendSensorEvent(@NonNull VirtualSensorEvent event) {
+ try {
+ mVirtualDevice.sendSensorEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
new file mode 100644
index 0000000..48b463a
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensorConfig;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
new file mode 100644
index 0000000..7982fa5
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Configuration for creation of a virtual sensor.
+ * @see VirtualSensor
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorConfig implements Parcelable {
+
+ private final int mType;
+ @NonNull
+ private final String mName;
+ @Nullable
+ private final String mVendor;
+ @Nullable
+ private final IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+ private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
+ @Nullable IVirtualSensorStateChangeCallback stateChangeCallback) {
+ mType = type;
+ mName = name;
+ mVendor = vendor;
+ mStateChangeCallback = stateChangeCallback;
+ }
+
+ private VirtualSensorConfig(@NonNull Parcel parcel) {
+ mType = parcel.readInt();
+ mName = parcel.readString8();
+ mVendor = parcel.readString8();
+ mStateChangeCallback =
+ IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+ parcel.writeString8(mName);
+ parcel.writeString8(mVendor);
+ parcel.writeStrongBinder(
+ mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null);
+ }
+
+ /**
+ * Returns the
+ * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the sensor, which must be unique per sensor type for each virtual device.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the vendor string of the sensor.
+ * @see Builder#setVendor
+ */
+ @Nullable
+ public String getVendor() {
+ return mVendor;
+ }
+
+ /**
+ * Returns the callback to get notified about changes in the sensor listeners.
+ * @hide
+ */
+ @Nullable
+ public IVirtualSensorStateChangeCallback getStateChangeCallback() {
+ return mStateChangeCallback;
+ }
+
+ /**
+ * Builder for {@link VirtualSensorConfig}.
+ */
+ public static final class Builder {
+
+ private final int mType;
+ @NonNull
+ private final String mName;
+ @Nullable
+ private String mVendor;
+ @Nullable
+ private IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+ private static class SensorStateChangeCallbackDelegate
+ extends IVirtualSensorStateChangeCallback.Stub {
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final VirtualSensor.SensorStateChangeCallback mCallback;
+
+ SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
+ @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+ @Override
+ public void onStateChanged(boolean enabled, int samplingPeriodMicros,
+ int batchReportLatencyMicros) {
+ final Duration samplingPeriod =
+ Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
+ final Duration batchReportingLatency =
+ Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
+ mExecutor.execute(() -> mCallback.onStateChanged(
+ enabled, samplingPeriod, batchReportingLatency));
+ }
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @param type The
+ * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ * @param name The name of the sensor. Must be unique among all sensors with the same type
+ * that belong to the same virtual device.
+ */
+ public Builder(int type, @NonNull String name) {
+ mType = type;
+ mName = Objects.requireNonNull(name);
+ }
+
+ /**
+ * Creates a new {@link VirtualSensorConfig}.
+ */
+ @NonNull
+ public VirtualSensorConfig build() {
+ return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback);
+ }
+
+ /**
+ * Sets the vendor string of the sensor.
+ */
+ @NonNull
+ public VirtualSensorConfig.Builder setVendor(@Nullable String vendor) {
+ mVendor = vendor;
+ return this;
+ }
+
+ /**
+ * Sets the callback to get notified about changes in the sensor listeners.
+ *
+ * @param executor The executor where the callback is executed on.
+ * @param callback The callback to get notified when the state of the sensor
+ * listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback}
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public VirtualSensorConfig.Builder setStateChangeCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+ mStateChangeCallback = new SensorStateChangeCallbackDelegate(
+ Objects.requireNonNull(executor),
+ Objects.requireNonNull(callback));
+ return this;
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<VirtualSensorConfig> CREATOR =
+ new Parcelable.Creator<>() {
+ public VirtualSensorConfig createFromParcel(Parcel source) {
+ return new VirtualSensorConfig(source);
+ }
+
+ public VirtualSensorConfig[] newArray(int size) {
+ return new VirtualSensorConfig[size];
+ }
+ };
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
new file mode 100644
index 0000000..9943946
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensorEvent;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
new file mode 100644
index 0000000..8f8860e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+
+/**
+ * A sensor event that originated from a virtual device's sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorEvent implements Parcelable {
+
+ @NonNull
+ private float[] mValues;
+ private long mTimestampNanos;
+
+ private VirtualSensorEvent(@NonNull float[] values, long timestampNanos) {
+ mValues = values;
+ mTimestampNanos = timestampNanos;
+ }
+
+ private VirtualSensorEvent(@NonNull Parcel parcel) {
+ final int valuesLength = parcel.readInt();
+ mValues = new float[valuesLength];
+ parcel.readFloatArray(mValues);
+ mTimestampNanos = parcel.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeInt(mValues.length);
+ parcel.writeFloatArray(mValues);
+ parcel.writeLong(mTimestampNanos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the values of this sensor event. The length and contents depend on the
+ * <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>.
+ * @see android.hardware.SensorEvent#values
+ */
+ @NonNull
+ public float[] getValues() {
+ return mValues;
+ }
+
+ /**
+ * The time in nanoseconds at which the event happened. For a given sensor, each new sensor
+ * event should be monotonically increasing.
+ *
+ * @see Builder#setTimestampNanos(long)
+ */
+ public long getTimestampNanos() {
+ return mTimestampNanos;
+ }
+
+ /**
+ * Builder for {@link VirtualSensorEvent}.
+ */
+ public static final class Builder {
+
+ @NonNull
+ private float[] mValues;
+ private long mTimestampNanos = 0;
+
+ /**
+ * Creates a new builder.
+ * @param values the values of the sensor event. @see android.hardware.SensorEvent#values
+ */
+ public Builder(@NonNull float[] values) {
+ mValues = values;
+ }
+
+ /**
+ * Creates a new {@link VirtualSensorEvent}.
+ */
+ @NonNull
+ public VirtualSensorEvent build() {
+ if (mValues == null || mValues.length == 0) {
+ throw new IllegalArgumentException(
+ "Cannot build virtual sensor event with no values.");
+ }
+ if (mTimestampNanos <= 0) {
+ mTimestampNanos = SystemClock.elapsedRealtimeNanos();
+ }
+ return new VirtualSensorEvent(mValues, mTimestampNanos);
+ }
+
+ /**
+ * Sets the timestamp of this event. For a given sensor, each new sensor event should be
+ * monotonically increasing using the same time base as
+ * {@link android.os.SystemClock#elapsedRealtimeNanos()}.
+ *
+ * If not explicitly set, the current timestamp is used for the sensor event.
+ *
+ * @see android.hardware.SensorEvent#timestamp
+ */
+ @NonNull
+ public Builder setTimestampNanos(long timestampNanos) {
+ mTimestampNanos = timestampNanos;
+ return this;
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<VirtualSensorEvent> CREATOR =
+ new Parcelable.Creator<>() {
+ public VirtualSensorEvent createFromParcel(Parcel source) {
+ return new VirtualSensorEvent(source);
+ }
+
+ public VirtualSensorEvent[] newArray(int size) {
+ return new VirtualSensorEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index cc7977a..99fc5a3 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,15 +16,21 @@
package android.content.om;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
+import android.util.TypedValue;
+import com.android.internal.content.om.OverlayManagerImpl;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
@@ -82,8 +88,24 @@
}
/**
+ * Constructs a builder for building a fabricated overlay.
+ *
+ * @param name a name used to uniquely identify the fabricated overlay owned by the caller
+ * itself.
+ * @param targetPackage the name of the package to overlay
+ */
+ public Builder(@NonNull String name, @NonNull String targetPackage) {
+ mName = OverlayManagerImpl.checkOverlayNameValid(name);
+ mTargetPackage =
+ Preconditions.checkStringNotEmpty(
+ targetPackage, "'targetPackage' must not be empty nor null");
+ mOwningPackage = ""; // The package name is filled in OverlayManager.commit
+ }
+
+ /**
* Sets the name of the overlayable resources to overlay (can be null).
*/
+ @NonNull
public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
return this;
@@ -111,45 +133,110 @@
}
/**
- * Sets the value of the fabricated overlay
+ * Sets the value of the fabricated overlay for the integer-like types.
*
* @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
+ * [package]:type/entry)
* @param dataType the data type of the new value
* @param value the unsigned 32 bit integer representing the new value
- *
+ * @return the builder itself
+ * @see #setResourceValue(String, int, int, String)
* @see android.util.TypedValue#type
*/
- public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+ int dataType,
+ int value) {
+ return setResourceValue(resourceName, dataType, value, null /* configuration */);
+ }
+
+ /**
+ * Sets the value of the fabricated overlay for the integer-like types with the
+ * configuration.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dataType the data type of the new value
+ * @param value the unsigned 32 bit integer representing the new value
+ * @param configuration The string representation of the config this overlay is enabled for
+ * @see android.util.TypedValue#type
+ */
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+ int dataType,
+ int value,
+ @Nullable String configuration) {
ensureValidResourceName(resourceName);
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
- entry.dataType = dataType;
+ entry.dataType =
+ Preconditions.checkArgumentInRange(
+ dataType,
+ TypedValue.TYPE_FIRST_INT,
+ TypedValue.TYPE_LAST_INT,
+ "dataType");
entry.data = value;
+ entry.configuration = configuration;
mEntries.add(entry);
return this;
}
+ /** @hide */
+ @IntDef(
+ prefix = {"OVERLAY_TYPE"},
+ value = {
+ TypedValue.TYPE_STRING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StringTypeOverlayResource {}
+
/**
- * Sets the value of the fabricated overlay
+ * Sets the value of the fabricated overlay for the string-like type.
*
* @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
+ * [package]:type/entry)
* @param dataType the data type of the new value
- * @param value the unsigned 32 bit integer representing the new value
- * @param configuration The string representation of the config this overlay is enabled for
- *
+ * @param value the string representing the new value
+ * @return the builder itself
* @see android.util.TypedValue#type
*/
- public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
- String configuration) {
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @StringTypeOverlayResource int dataType,
+ @NonNull String value) {
+ return setResourceValue(resourceName, dataType, value, null /* configuration */);
+ }
+
+ /**
+ * Sets the value of the fabricated overlay for the string-like type with the configuration.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dataType the data type of the new value
+ * @param value the string representing the new value
+ * @param configuration The string representation of the config this overlay is enabled for
+ * @see android.util.TypedValue#type
+ */
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @StringTypeOverlayResource int dataType,
+ @NonNull String value,
+ @Nullable String configuration) {
ensureValidResourceName(resourceName);
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
- entry.dataType = dataType;
- entry.data = value;
+ entry.dataType =
+ Preconditions.checkArgumentInRange(
+ dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType");
+ entry.stringData = Objects.requireNonNull(value);
entry.configuration = configuration;
mEntries.add(entry);
return this;
@@ -159,68 +246,32 @@
* Sets the value of the fabricated overlay
*
* @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
- * @param dataType the data type of the new value
- * @param value the string representing the new value
- *
- * @see android.util.TypedValue#type
- */
- public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
- ensureValidResourceName(resourceName);
-
- final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
- entry.resourceName = resourceName;
- entry.dataType = dataType;
- entry.stringData = value;
- mEntries.add(entry);
- return this;
- }
-
- /**
- * Sets the value of the fabricated overlay
- *
- * @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
- * @param dataType the data type of the new value
- * @param value the string representing the new value
- * @param configuration The string representation of the config this overlay is enabled for
- *
- * @see android.util.TypedValue#type
- */
- public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
- String configuration) {
- ensureValidResourceName(resourceName);
-
- final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
- entry.resourceName = resourceName;
- entry.dataType = dataType;
- entry.stringData = value;
- entry.configuration = configuration;
- mEntries.add(entry);
- return this;
- }
-
- /**
- * Sets the value of the fabricated overlay
- *
- * @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
+ * [package]:type/entry)
* @param value the file descriptor whose contents are the value of the frro
* @param configuration The string representation of the config this overlay is enabled for
+ * @return the builder itself
*/
- public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
- String configuration) {
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @NonNull ParcelFileDescriptor value,
+ @Nullable String configuration) {
ensureValidResourceName(resourceName);
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
- entry.binaryData = value;
+ entry.binaryData = Objects.requireNonNull(value);
entry.configuration = configuration;
mEntries.add(entry);
return this;
}
- /** Builds an immutable fabricated overlay. */
+ /**
+ * Builds an immutable fabricated overlay.
+ *
+ * @return the fabricated overlay
+ */
+ @NonNull
public FabricatedOverlay build() {
final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
overlay.packageName = mOwningPackage;
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 12911d6..1e928bd 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -23,6 +23,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
import android.content.IntentSender;
+import android.os.RemoteCallback;
import android.graphics.Bitmap;
@@ -66,4 +67,6 @@
void setAllowUnlimitedSilentUpdates(String installerPackageName);
void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
+ void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
+ in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
}
diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl
index 833919e..ab9d4f3 100644
--- a/core/java/android/content/pm/PackageInstaller.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -16,6 +16,7 @@
package android.content.pm;
+parcelable PackageInstaller.InstallConstraints;
parcelable PackageInstaller.SessionParams;
parcelable PackageInstaller.SessionInfo;
parcelable PackageInstaller.PreapprovalDetails;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 3551827..c79f99d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -36,6 +36,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityManager;
@@ -57,6 +58,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.ParcelableException;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -89,6 +91,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Offers the ability to install, upgrade, and remove applications on the
@@ -854,6 +857,29 @@
}
/**
+ * Check if install constraints are satisfied for the given packages.
+ *
+ * Note this query result is just a hint and subject to race because system states could
+ * change anytime in-between this query and committing the session.
+ *
+ * The result is returned by a callback because some constraints might take a long time
+ * to evaluate.
+ */
+ public void checkInstallConstraints(@NonNull List<String> packageNames,
+ @NonNull InstallConstraints constraints,
+ @NonNull Consumer<InstallConstraintsResult> callback) {
+ try {
+ var remoteCallback = new RemoteCallback(b -> {
+ callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+ });
+ mInstaller.checkInstallConstraints(
+ mInstallerPackageName, packageNames, constraints, remoteCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Events for observing session lifecycle.
* <p>
* A typical session lifecycle looks like this:
@@ -3647,4 +3673,362 @@
// End of generated code
}
+
+ /**
+ * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+ */
+ @DataClass(genParcelable = true, genHiddenConstructor = true)
+ public static final class InstallConstraintsResult implements Parcelable {
+ /**
+ * True if all constraints are satisfied.
+ */
+ private boolean mAllConstraintsSatisfied;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new InstallConstraintsResult.
+ *
+ * @param allConstraintsSatisfied
+ * True if all constraints are satisfied.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public InstallConstraintsResult(
+ boolean allConstraintsSatisfied) {
+ this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * True if all constraints are satisfied.
+ */
+ @DataClass.Generated.Member
+ public boolean isAllConstraintsSatisfied() {
+ return mAllConstraintsSatisfied;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mAllConstraintsSatisfied) flg |= 0x1;
+ dest.writeByte(flg);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InstallConstraintsResult(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean allConstraintsSatisfied = (flg & 0x1) != 0;
+
+ this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InstallConstraintsResult> CREATOR
+ = new Parcelable.Creator<InstallConstraintsResult>() {
+ @Override
+ public InstallConstraintsResult[] newArray(int size) {
+ return new InstallConstraintsResult[size];
+ }
+
+ @Override
+ public InstallConstraintsResult createFromParcel(@NonNull Parcel in) {
+ return new InstallConstraintsResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1668650523745L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+ inputSignatures = "private boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
+ /**
+ * A class to encapsulate constraints for installation.
+ *
+ * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+ * specifies the conditions to check against for the packages in question. This can be used
+ * by app stores to deliver auto updates without disrupting the user experience (referred as
+ * gentle update) - for example, an app store might hold off updates when it find out the
+ * app to update is interacting with the user.
+ *
+ * Use {@link Builder} to create a new instance and call mutator methods to add constraints.
+ * If no mutators were called, default constraints will be generated which implies no
+ * constraints. It is recommended to use preset constraints which are useful in most
+ * cases.
+ *
+ * For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE}
+ * for the system knows best how to do it. It will also benefits the installer as the
+ * platform evolves and add more constraints to improve the accuracy and efficiency of
+ * gentle update.
+ *
+ * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
+ * library or bounded service), the constraints will also be applied to Bar.
+ */
+ @DataClass(genParcelable = true, genHiddenConstructor = true)
+ public static final class InstallConstraints implements Parcelable {
+ /**
+ * Preset constraints suitable for gentle update.
+ */
+ @NonNull
+ public static final InstallConstraints GENTLE_UPDATE =
+ new Builder().requireAppNotInteracting().build();
+
+ private final boolean mRequireDeviceIdle;
+ private final boolean mRequireAppNotForeground;
+ private final boolean mRequireAppNotInteracting;
+ private final boolean mRequireAppNotTopVisible;
+ private final boolean mRequireNotInCall;
+
+ /**
+ * Builder class for constructing {@link InstallConstraints}.
+ */
+ public static final class Builder {
+ private boolean mRequireDeviceIdle;
+ private boolean mRequireAppNotForeground;
+ private boolean mRequireAppNotInteracting;
+ private boolean mRequireAppNotTopVisible;
+ private boolean mRequireNotInCall;
+
+ /**
+ * This constraint requires the device is idle.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireDeviceIdle() {
+ mRequireDeviceIdle = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires the app in question is not in the foreground.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireAppNotForeground() {
+ mRequireAppNotForeground = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires the app in question is not interacting with the user.
+ * User interaction includes:
+ * <ul>
+ * <li>playing or recording audio/video</li>
+ * <li>sending or receiving network data</li>
+ * <li>being visible to the user</li>
+ * </ul>
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireAppNotInteracting() {
+ mRequireAppNotInteracting = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires the app in question is not top-visible to the user.
+ * A top-visible app is showing UI at the top of the screen that the user is
+ * interacting with.
+ *
+ * Note this constraint is a subset of {@link #requireAppNotForeground()}
+ * because a top-visible app is also a foreground app. This is also a subset
+ * of {@link #requireAppNotInteracting()} because a top-visible app is interacting
+ * with the user.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireAppNotTopVisible() {
+ mRequireAppNotTopVisible = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires there is no ongoing call in the device.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireNotInCall() {
+ mRequireNotInCall = true;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link InstallConstraints} instance.
+ */
+ @NonNull
+ public InstallConstraints build() {
+ return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
+ mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new InstallConstraints.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public InstallConstraints(
+ boolean requireDeviceIdle,
+ boolean requireAppNotForeground,
+ boolean requireAppNotInteracting,
+ boolean requireAppNotTopVisible,
+ boolean requireNotInCall) {
+ this.mRequireDeviceIdle = requireDeviceIdle;
+ this.mRequireAppNotForeground = requireAppNotForeground;
+ this.mRequireAppNotInteracting = requireAppNotInteracting;
+ this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+ this.mRequireNotInCall = requireNotInCall;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireDeviceIdle() {
+ return mRequireDeviceIdle;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireAppNotForeground() {
+ return mRequireAppNotForeground;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireAppNotInteracting() {
+ return mRequireAppNotInteracting;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireAppNotTopVisible() {
+ return mRequireAppNotTopVisible;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireNotInCall() {
+ return mRequireNotInCall;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mRequireDeviceIdle) flg |= 0x1;
+ if (mRequireAppNotForeground) flg |= 0x2;
+ if (mRequireAppNotInteracting) flg |= 0x4;
+ if (mRequireAppNotTopVisible) flg |= 0x8;
+ if (mRequireNotInCall) flg |= 0x10;
+ dest.writeByte(flg);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InstallConstraints(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean requireDeviceIdle = (flg & 0x1) != 0;
+ boolean requireAppNotForeground = (flg & 0x2) != 0;
+ boolean requireAppNotInteracting = (flg & 0x4) != 0;
+ boolean requireAppNotTopVisible = (flg & 0x8) != 0;
+ boolean requireNotInCall = (flg & 0x10) != 0;
+
+ this.mRequireDeviceIdle = requireDeviceIdle;
+ this.mRequireAppNotForeground = requireAppNotForeground;
+ this.mRequireAppNotInteracting = requireAppNotInteracting;
+ this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+ this.mRequireNotInCall = requireNotInCall;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InstallConstraints> CREATOR
+ = new Parcelable.Creator<InstallConstraints>() {
+ @Override
+ public InstallConstraints[] newArray(int size) {
+ return new InstallConstraints[size];
+ }
+
+ @Override
+ public InstallConstraints createFromParcel(@NonNull Parcel in) {
+ return new InstallConstraints(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1668650523752L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+ inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1c4898a..18118f5 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,8 +16,14 @@
package android.hardware;
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -45,6 +51,7 @@
import java.io.UncheckedIOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -80,6 +87,8 @@
private static native boolean nativeGetSensorAtIndex(long nativeInstance,
Sensor sensor, int index);
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
+ private static native void nativeGetRuntimeSensors(
+ long nativeInstance, int deviceId, List<Sensor> list);
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
private static native int nativeCreateDirectChannel(
@@ -100,6 +109,10 @@
private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
private List<Sensor> mFullDynamicSensorsList = new ArrayList<>();
+ private final SparseArray<List<Sensor>> mFullRuntimeSensorListByDevice = new SparseArray<>();
+ private final SparseArray<SparseArray<List<Sensor>>> mRuntimeSensorListByDeviceByType =
+ new SparseArray<>();
+
private boolean mDynamicSensorListDirty = true;
private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>();
@@ -114,6 +127,7 @@
private HashMap<DynamicSensorCallback, Handler>
mDynamicSensorCallbacks = new HashMap<>();
private BroadcastReceiver mDynamicSensorBroadcastReceiver;
+ private BroadcastReceiver mRuntimeSensorBroadcastReceiver;
// Looper associated with the context in which this instance was created.
private final Looper mMainLooper;
@@ -121,6 +135,7 @@
private final boolean mIsPackageDebuggable;
private final Context mContext;
private final long mNativeInstance;
+ private final VirtualDeviceManager mVdm;
private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
@@ -139,6 +154,7 @@
mContext = context;
mNativeInstance = nativeCreate(context.getOpPackageName());
mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+ mVdm = mContext.getSystemService(VirtualDeviceManager.class);
// initialize the sensor list
for (int index = 0;; ++index) {
@@ -147,12 +163,63 @@
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
+
+ }
+
+ /** @hide */
+ @Override
+ public List<Sensor> getSensorList(int type) {
+ final int deviceId = mContext.getDeviceId();
+ if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+ || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+ return super.getSensorList(type);
+ }
+
+ // Cache the per-device lists on demand.
+ List<Sensor> list;
+ synchronized (mFullRuntimeSensorListByDevice) {
+ List<Sensor> fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+ if (fullList == null) {
+ fullList = createRuntimeSensorListLocked(deviceId);
+ }
+ SparseArray<List<Sensor>> deviceSensorListByType =
+ mRuntimeSensorListByDeviceByType.get(deviceId);
+ list = deviceSensorListByType.get(type);
+ if (list == null) {
+ if (type == Sensor.TYPE_ALL) {
+ list = fullList;
+ } else {
+ list = new ArrayList<>();
+ for (Sensor i : fullList) {
+ if (i.getType() == type) {
+ list.add(i);
+ }
+ }
+ }
+ list = Collections.unmodifiableList(list);
+ deviceSensorListByType.append(type, list);
+ }
+ }
+ return list;
}
/** @hide */
@Override
protected List<Sensor> getFullSensorList() {
- return mFullSensorsList;
+ final int deviceId = mContext.getDeviceId();
+ if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+ || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+ return mFullSensorsList;
+ }
+
+ List<Sensor> fullList;
+ synchronized (mFullRuntimeSensorListByDevice) {
+ fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+ if (fullList == null) {
+ fullList = createRuntimeSensorListLocked(deviceId);
+ }
+ }
+ return fullList;
}
/** @hide */
@@ -446,12 +513,53 @@
}
}
+ private List<Sensor> createRuntimeSensorListLocked(int deviceId) {
+ setupRuntimeSensorBroadcastReceiver();
+ List<Sensor> list = new ArrayList<>();
+ nativeGetRuntimeSensors(mNativeInstance, deviceId, list);
+ mFullRuntimeSensorListByDevice.put(deviceId, list);
+ mRuntimeSensorListByDeviceByType.put(deviceId, new SparseArray<>());
+ for (Sensor s : list) {
+ mHandleToSensor.put(s.getHandle(), s);
+ }
+ return list;
+ }
+
+ private void setupRuntimeSensorBroadcastReceiver() {
+ if (mRuntimeSensorBroadcastReceiver == null) {
+ mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
+ synchronized (mFullRuntimeSensorListByDevice) {
+ final int deviceId = intent.getIntExtra(
+ EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID);
+ List<Sensor> removedSensors =
+ mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
+ if (removedSensors != null) {
+ for (Sensor s : removedSensors) {
+ cleanupSensorConnection(s);
+ }
+ }
+ mRuntimeSensorListByDeviceByType.remove(deviceId);
+ }
+ }
+ }
+ };
+
+ IntentFilter filter = new IntentFilter("virtual_device_removed");
+ filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED);
+ mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+ }
+
private void setupDynamicSensorBroadcastReceiver() {
if (mDynamicSensorBroadcastReceiver == null) {
mDynamicSensorBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) {
+ if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
if (DEBUG_DYNAMIC_SENSOR) {
Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 9b07d3a..441fd88 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -137,7 +137,8 @@
VIRTUAL_DISPLAY_FLAG_TRUSTED,
VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED,
- VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
+ VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED,
+ VIRTUAL_DISPLAY_FLAG_OWN_FOCUS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface VirtualDisplayFlag {}
@@ -403,6 +404,22 @@
*/
public static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13;
+ /**
+ * Virtual display flags: Indicates that the display maintains its own focus and touch mode.
+ *
+ * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+ * behavior, but only applies to the specific display instead of system-wide to all displays.
+ *
+ * Note: The display must be trusted in order to have its own focus.
+ *
+ * @see #createVirtualDisplay
+ * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @hide
+ */
+ @TestApi
+ public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
+
+
/** @hide */
@IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
MATCH_CONTENT_FRAMERATE_UNKNOWN,
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index ade9fd6..b2dfd85 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -24,6 +24,8 @@
import android.os.Parcelable;
import android.util.ArrayMap;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -41,14 +43,25 @@
public final class ProgramList implements AutoCloseable {
private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
new ArrayMap<>();
+ @GuardedBy("mLock")
private final List<ListCallback> mListCallbacks = new ArrayList<>();
+
+ @GuardedBy("mLock")
private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+
+ @GuardedBy("mLock")
private OnCloseListener mOnCloseListener;
- private boolean mIsClosed = false;
- private boolean mIsComplete = false;
+
+ @GuardedBy("mLock")
+ private boolean mIsClosed;
+
+ @GuardedBy("mLock")
+ private boolean mIsComplete;
ProgramList() {}
@@ -227,6 +240,7 @@
}
}
+ @GuardedBy("mLock")
private void putLocked(RadioManager.ProgramInfo value,
List<ProgramSelector.Identifier> changedIdentifierList) {
ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
@@ -235,6 +249,7 @@
changedIdentifierList.add(sel);
}
+ @GuardedBy("mLock")
private void removeLocked(ProgramSelector.Identifier key,
List<ProgramSelector.Identifier> removedIdentifierList) {
RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index a887f2a..eae7ce0 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -58,6 +58,7 @@
void setUserIcon(int userId, in Bitmap icon);
ParcelFileDescriptor getUserIcon(int userId);
UserInfo getPrimaryUser();
+ int getMainUserId();
List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
List<UserInfo> getProfiles(int userId, boolean enabledOnly);
int[] getProfileIds(int userId, boolean enabledOnly);
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9ea4278..394927e 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -252,10 +252,12 @@
}
/**
- * Returns the list of declared instances for an interface.
+ * Returns an array of all declared instances for a particular interface.
*
- * @return true if the service is declared somewhere (eg. VINTF manifest) and
- * waitForService should always be able to return the service.
+ * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF
+ * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be
+ * returned.
+ *
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1f21bfe..954d1fc 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2377,14 +2377,16 @@
}
/**
- * Returns true if the context user is the designated "main user" of the device. This user may
- * have access to certain features which are limited to at most one user.
+ * Returns {@code true} if the context user is the designated "main user" of the device. This
+ * user may have access to certain features which are limited to at most one user. There will
+ * never be more than one main user on a device.
*
- * <p>Currently, the first human user on the device will be the main user; in the future, the
- * concept may be transferable, so a different user (or even no user at all) may be designated
- * the main user instead.
+ * <p>Currently, on most form factors the first human user on the device will be the main user;
+ * in the future, the concept may be transferable, so a different user (or even no user at all)
+ * may be designated the main user instead. On other form factors there might not be a main
+ * user.
*
- * <p>Note that this will be the not be the system user on devices for which
+ * <p>Note that this will not be the system user on devices for which
* {@link #isHeadlessSystemUserMode()} returns true.
* @hide
*/
@@ -2400,6 +2402,29 @@
}
/**
+ * Returns the designated "main user" of the device, or {@code null} if there is no main user.
+ *
+ * @see #isMainUser()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public @Nullable UserHandle getMainUser() {
+ try {
+ final int mainUserId = mService.getMainUserId();
+ if (mainUserId == UserHandle.USER_NULL) {
+ return null;
+ }
+ return UserHandle.of(mainUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Used to check if the context user is an admin user. An admin user is allowed to
* modify or configure certain settings that aren't available to non-admin users,
* create and delete additional users, etc. There can be more than one admin users.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index aebd91a..84a233f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2183,6 +2183,17 @@
}
}
+ /**
+ * Returns a Looper which messages such as {@link WallpaperService#DO_ATTACH},
+ * {@link WallpaperService#DO_DETACH} etc. are sent to.
+ * By default, returns the process's main looper.
+ * @hide
+ */
+ @NonNull
+ public Looper onProvideEngineLooper() {
+ return super.getMainLooper();
+ }
+
private boolean isValid(RectF area) {
if (area == null) return false;
boolean valid = area.bottom > area.top && area.left < area.right
@@ -2215,12 +2226,12 @@
Engine mEngine;
@SetWallpaperFlags int mWhich;
- IWallpaperEngineWrapper(WallpaperService context,
+ IWallpaperEngineWrapper(WallpaperService service,
IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
int displayId, @SetWallpaperFlags int which) {
mWallpaperManager = getSystemService(WallpaperManager.class);
- mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
+ mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
mConnection = conn;
mWindowToken = windowToken;
mWindowType = windowType;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4afd268..608cbda 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -122,6 +122,13 @@
public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD = "settings_new_keyboard_trackpad";
/**
+ * Enable trackpad gesture settings UI
+ * @hide
+ */
+ public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE =
+ "settings_new_keyboard_trackpad_gesture";
+
+ /**
* Enable the new pages which is implemented with SPA.
* @hide
*/
@@ -171,6 +178,7 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
@@ -190,6 +198,7 @@
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
+ PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
}
/**
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 7b28b8a..bc0e35d 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -234,4 +234,23 @@
public int[] toArray() {
return Arrays.copyOf(mValues, mSize);
}
+
+ @Override
+ public String toString() {
+ // Code below is copied from Arrays.toString(), but uses mSize in the lopp (it cannot call
+ // Arrays.toString() directly as it would return the unused elements as well)
+ int iMax = mSize - 1;
+ if (iMax == -1) {
+ return "[]";
+ }
+ StringBuilder b = new StringBuilder();
+ b.append('[');
+ for (int i = 0;; i++) {
+ b.append(mValues[i]);
+ if (i == iMax) {
+ return b.append(']').toString();
+ }
+ b.append(", ");
+ }
+ }
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5933ae4..fbca373 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -319,6 +319,19 @@
public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10;
/**
+ * Flag: Indicates that the display maintains its own focus and touch mode.
+ *
+ * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+ * behavior, but only applies to the specific display instead of system-wide to all displays.
+ *
+ * Note: The display must be trusted in order to have its own focus.
+ *
+ * @see #FLAG_TRUSTED
+ * @hide
+ */
+ public static final int FLAG_OWN_FOCUS = 1 << 11;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e2bc566..0743ccb 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -738,9 +738,8 @@
* If invoked through a package other than a launcher app, returns an empty list.
*
* @param displayId the id of the logical display
- * @param packageName the name of the calling package
*/
- List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+ List<DisplayInfo> getPossibleDisplayInfo(int displayId);
/**
* Called to show global actions.
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 57b2d39..33ea92d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -947,108 +947,112 @@
+ " left=" + (mWindowSpaceLeft != mLocation[0])
+ " top=" + (mWindowSpaceTop != mLocation[1]));
- mVisible = mRequestedVisible;
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- mSurfaceWidth = myWidth;
- mSurfaceHeight = myHeight;
- mFormat = mRequestedFormat;
- mAlpha = alpha;
- mLastWindowVisibility = mWindowVisibility;
- mTransformHint = viewRoot.getBufferTransformHint();
- mSubLayer = mRequestedSubLayer;
-
- mScreenRect.left = mWindowSpaceLeft;
- mScreenRect.top = mWindowSpaceTop;
- mScreenRect.right = mWindowSpaceLeft + getWidth();
- mScreenRect.bottom = mWindowSpaceTop + getHeight();
- if (translator != null) {
- translator.translateRectInAppWindowToScreen(mScreenRect);
- }
-
- 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 surfaceUpdateTransaction = new Transaction();
- if (creating) {
- updateOpaqueFlag();
- final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
- createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
- } else if (mSurfaceControl == null) {
- return;
- }
-
- final boolean redrawNeeded = sizeChanged || creating || hintChanged
- || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
- boolean shouldSyncBuffer =
- redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
- SyncBufferTransactionCallback syncBufferTransactionCallback = null;
- if (shouldSyncBuffer) {
- syncBufferTransactionCallback = new SyncBufferTransactionCallback();
- mBlastBufferQueue.syncNextTransaction(
- false /* acquireSingleBuffer */,
- syncBufferTransactionCallback::onTransactionReady);
- }
-
- final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
- creating, sizeChanged, hintChanged, relativeZChanged,
- surfaceUpdateTransaction);
-
try {
- SurfaceHolder.Callback[] callbacks = null;
+ mVisible = mRequestedVisible;
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
+ mFormat = mRequestedFormat;
+ mAlpha = alpha;
+ mLastWindowVisibility = mWindowVisibility;
+ mTransformHint = viewRoot.getBufferTransformHint();
+ mSubLayer = mRequestedSubLayer;
- final boolean surfaceChanged = creating;
- if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
- mSurfaceCreated = false;
- notifySurfaceDestroyed();
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ if (translator != null) {
+ translator.translateRectInAppWindowToScreen(mScreenRect);
}
- copySurface(creating /* surfaceControlCreated */, sizeChanged);
+ 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 surfaceUpdateTransaction = new Transaction();
+ if (creating) {
+ updateOpaqueFlag();
+ final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+ createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+ } else if (mSurfaceControl == null) {
+ return;
+ }
- if (mVisible && mSurface.isValid()) {
- if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
- mSurfaceCreated = true;
- mIsCreating = true;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "visibleChanged -- surfaceCreated");
- callbacks = getSurfaceCallbacks();
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceCreated(mSurfaceHolder);
- }
+ final boolean redrawNeeded = sizeChanged || creating || hintChanged
+ || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
+ boolean shouldSyncBuffer =
+ redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+ SyncBufferTransactionCallback syncBufferTransactionCallback = null;
+ if (shouldSyncBuffer) {
+ syncBufferTransactionCallback = new SyncBufferTransactionCallback();
+ mBlastBufferQueue.syncNextTransaction(
+ false /* acquireSingleBuffer */,
+ syncBufferTransactionCallback::onTransactionReady);
+ }
+
+ final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
+ creating, sizeChanged, hintChanged, relativeZChanged,
+ surfaceUpdateTransaction);
+
+ try {
+ SurfaceHolder.Callback[] callbacks = null;
+
+ final boolean surfaceChanged = creating;
+ if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
+ mSurfaceCreated = false;
+ notifySurfaceDestroyed();
}
- if (creating || formatChanged || sizeChanged || hintChanged
- || visibleChanged || realSizeChanged) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "surfaceChanged -- format=" + mFormat
- + " w=" + myWidth + " h=" + myHeight);
- if (callbacks == null) {
+
+ copySurface(creating /* surfaceControlCreated */, sizeChanged);
+
+ if (mVisible && mSurface.isValid()) {
+ if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ mSurfaceCreated = true;
+ mIsCreating = true;
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceCreated");
callbacks = getSurfaceCallbacks();
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
}
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ if (creating || formatChanged || sizeChanged || hintChanged
+ || visibleChanged || realSizeChanged) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceChanged -- format=" + mFormat
+ + " w=" + myWidth + " h=" + myHeight);
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ }
}
- }
- if (redrawNeeded) {
- if (DEBUG) {
- Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
- }
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
+ if (redrawNeeded) {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+ }
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
- if (shouldSyncBuffer) {
- handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
- } else {
- handleSyncNoBuffer(callbacks);
+ if (shouldSyncBuffer) {
+ handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
+ } else {
+ handleSyncNoBuffer(callbacks);
+ }
}
}
+ } finally {
+ mIsCreating = false;
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ releaseSurfaces(false /* releaseSurfacePackage*/);
+ }
}
- } finally {
- mIsCreating = false;
- if (mSurfaceControl != null && !mSurfaceCreated) {
- releaseSurfaces(false /* releaseSurfacePackage*/);
- }
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 6dc9011..5c4305c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -366,7 +366,7 @@
List<DisplayInfo> possibleDisplayInfos;
try {
possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
- .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+ .getPossibleDisplayInfo(displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index c2da638..a35e13e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,6 +421,53 @@
return false;
}
+ /**
+ * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
+ * This includes root-leash and snapshots.
+ */
+ public void releaseAnimSurfaces() {
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ final Change c = mChanges.get(i);
+ if (c.mSnapshot != null) {
+ c.mSnapshot.release();
+ c.mSnapshot = null;
+ }
+ }
+ if (mRootLeash != null) {
+ mRootLeash.release();
+ }
+ }
+
+ /**
+ * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
+ * if the surface-controls get stored and used elsewhere in the process. To just release
+ * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
+ */
+ public void releaseAllSurfaces() {
+ releaseAnimSurfaces();
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ mChanges.get(i).getLeash().release();
+ }
+ }
+
+ /**
+ * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
+ * refcounts are incremented which allows the "remote" receiver to release them without breaking
+ * the caller's references. Use this only if you need to "send" this to a local function which
+ * assumes it is being called from a remote caller.
+ */
+ public TransitionInfo localRemoteCopy() {
+ final TransitionInfo out = new TransitionInfo(mType, mFlags);
+ for (int i = 0; i < mChanges.size(); ++i) {
+ out.mChanges.add(mChanges.get(i).localRemoteCopy());
+ }
+ out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+ // Doesn't have any native stuff, so no need for actual copy
+ out.mOptions = mOptions;
+ out.mRootOffset.set(mRootOffset);
+ return out;
+ }
+
/** Represents the change a WindowContainer undergoes during a transition */
public static final class Change implements Parcelable {
private final WindowContainerToken mContainer;
@@ -473,6 +520,27 @@
mSnapshotLuma = in.readFloat();
}
+ private Change localRemoteCopy() {
+ final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
+ out.mParent = mParent;
+ out.mLastParent = mLastParent;
+ out.mMode = mMode;
+ out.mFlags = mFlags;
+ out.mStartAbsBounds.set(mStartAbsBounds);
+ out.mEndAbsBounds.set(mEndAbsBounds);
+ out.mEndRelOffset.set(mEndRelOffset);
+ out.mTaskInfo = mTaskInfo;
+ out.mAllowEnterPip = mAllowEnterPip;
+ out.mStartRotation = mStartRotation;
+ out.mEndRotation = mEndRotation;
+ out.mEndFixedRotation = mEndFixedRotation;
+ out.mRotationAnimation = mRotationAnimation;
+ out.mBackgroundColor = mBackgroundColor;
+ out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
+ out.mSnapshotLuma = mSnapshotLuma;
+ return out;
+ }
+
/** Sets the parent of this change's container. The parent must be a participant or null. */
public void setParent(@Nullable WindowContainerToken parent) {
mParent = parent;
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index 6ceccd1..260d1a2 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -192,7 +192,7 @@
* @param name the non-check overlay name
* @return the valid overlay name
*/
- private static String checkOverlayNameValid(@NonNull String name) {
+ public static String checkOverlayNameValid(@NonNull String name) {
final String overlayName =
Preconditions.checkStringNotEmpty(
name, "overlayName should be neither empty nor null string");
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f140e79..1bc903a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -40,6 +40,8 @@
cppflags: ["-Wno-conversion-null"],
+ cpp_std: "gnu++20",
+
srcs: [
"android_animation_PropertyValuesHolder.cpp",
"android_os_SystemClock.cpp",
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index cb97698..939a0e4 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -243,6 +243,23 @@
}
}
+static void nativeGetRuntimeSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jint deviceId,
+ jobject sensorList) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+ const ListOffsets &listOffsets(gListOffsets);
+
+ Vector<Sensor> nativeList;
+
+ mgr->getRuntimeSensorList(deviceId, nativeList);
+
+ ALOGI("DYNS native SensorManager.getRuntimeSensorList return %zu sensors", nativeList.size());
+ for (size_t i = 0; i < nativeList.size(); ++i) {
+ jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
+ // add to list
+ env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
+ }
+}
+
static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
return mgr->isDataInjectionEnabled();
@@ -503,40 +520,26 @@
//----------------------------------------------------------------------------
static const JNINativeMethod gSystemSensorManagerMethods[] = {
- {"nativeClassInit",
- "()V",
- (void*)nativeClassInit },
- {"nativeCreate",
- "(Ljava/lang/String;)J",
- (void*)nativeCreate },
+ {"nativeClassInit", "()V", (void *)nativeClassInit},
+ {"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate},
- {"nativeGetSensorAtIndex",
- "(JLandroid/hardware/Sensor;I)Z",
- (void*)nativeGetSensorAtIndex },
+ {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+ (void *)nativeGetSensorAtIndex},
- {"nativeGetDynamicSensors",
- "(JLjava/util/List;)V",
- (void*)nativeGetDynamicSensors },
+ {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
- {"nativeIsDataInjectionEnabled",
- "(J)Z",
- (void*)nativeIsDataInjectionEnabled },
+ {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
- {"nativeCreateDirectChannel",
- "(JJIILandroid/hardware/HardwareBuffer;)I",
- (void*)nativeCreateDirectChannel },
+ {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
- {"nativeDestroyDirectChannel",
- "(JI)V",
- (void*)nativeDestroyDirectChannel },
+ {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
+ (void *)nativeCreateDirectChannel},
- {"nativeConfigDirectChannel",
- "(JIII)I",
- (void*)nativeConfigDirectChannel },
+ {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
- {"nativeSetOperationParameter",
- "(JII[F[I)I",
- (void*)nativeSetOperationParameter },
+ {"nativeConfigDirectChannel", "(JIII)I", (void *)nativeConfigDirectChannel},
+
+ {"nativeSetOperationParameter", "(JII[F[I)I", (void *)nativeSetOperationParameter},
};
static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ecc3979..ad8f7fb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6287,12 +6287,12 @@
<!-- Allows a regular application to use {@link android.app.Service#startForeground
Service.startForeground} with the type "specialUse".
- <p>Protection level: signature|appop|instant
+ <p>Protection level: normal|appop|instant
-->
<permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
android:description="@string/permdesc_foregroundServiceSpecialUse"
android:label="@string/permlab_foregroundServiceSpecialUse"
- android:protectionLevel="signature|appop|instant" />
+ android:protectionLevel="normal|appop|instant" />
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
@@ -7291,6 +7291,10 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.pm.GentleUpdateHelper$Service"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service
android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d832bc..7946493 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8955,6 +8955,9 @@
<!-- Flag indicating whether a recognition service can be selected as default. The default
value of this flag is true. -->
<attr name="selectableAsDefault" format="boolean" />
+ <!-- The maximal number of recognition sessions ongoing at the same time.
+ The default value is 1, meaning no concurrency. -->
+ <attr name="maxConcurrentSessionsCount" format="integer" />
</declare-styleable>
<!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ccce9ba..9c2643b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1828,6 +1828,14 @@
config_enableFusedLocationOverlay is false. -->
<string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
+ <!-- If true will use the GNSS hardware implementation to service the GPS_PROVIDER. If false
+ will allow the GPS_PROVIDER to be replaced by an app at run-time (restricted to the package
+ specified by config_gnssLocationProviderPackageName). -->
+ <bool name="config_useGnssHardwareProvider" translatable="false">true</bool>
+ <!-- Package name providing GNSS location support. Used only when
+ config_useGnssHardwareProvider is false. -->
+ <string name="config_gnssLocationProviderPackageName" translatable="false">@null</string>
+
<!-- Default value for the ADAS GNSS Location Enabled setting if this setting has never been
set before. -->
<bool name="config_defaultAdasGnssLocationEnabled" translatable="false">false</bool>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 61229cb..bc5878a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -117,6 +117,7 @@
<public name="accessibilityDataPrivate" />
<public name="enableTextStylingShortcuts" />
<public name="targetDisplayCategory"/>
+ <public name="maxConcurrentSessionsCount" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ae033ca..cd93932 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1963,6 +1963,7 @@
<java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
+ <java-symbol type="bool" name="config_useGnssHardwareProvider" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
<java-symbol type="bool" name="config_enableGeofenceOverlay" />
<java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
@@ -2125,6 +2126,7 @@
<java-symbol type="string" name="config_datause_iface" />
<java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
<java-symbol type="string" name="config_fusedLocationProviderPackageName" />
+ <java-symbol type="string" name="config_gnssLocationProviderPackageName" />
<java-symbol type="string" name="config_geocoderProviderPackageName" />
<java-symbol type="string" name="config_geofenceProviderPackageName" />
<java-symbol type="string" name="config_networkLocationProviderPackageName" />
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
new file mode 100644
index 0000000..11afd04
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Duration;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorConfigTest {
+
+ private static final String SENSOR_NAME = "VirtualSensorName";
+ private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private VirtualSensor.SensorStateChangeCallback mSensorCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void parcelAndUnparcel_matches() {
+ final VirtualSensorConfig originalConfig =
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setVendor(SENSOR_VENDOR)
+ .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+ .build();
+ final Parcel parcel = Parcel.obtain();
+ originalConfig.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ final VirtualSensorConfig recreatedConfig =
+ VirtualSensorConfig.CREATOR.createFromParcel(parcel);
+ assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
+ assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
+ assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
+ assertThat(recreatedConfig.getStateChangeCallback()).isNotNull();
+ }
+
+ @Test
+ public void sensorConfig_onlyRequiredFields() {
+ final VirtualSensorConfig config =
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
+ assertThat(config.getVendor()).isNull();
+ assertThat(config.getStateChangeCallback()).isNull();
+ }
+
+ @Test
+ public void sensorConfig_sensorCallbackInvocation() throws Exception {
+ final VirtualSensorConfig config =
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+ .build();
+
+ final Duration samplingPeriod = Duration.ofMillis(123);
+ final Duration batchLatency = Duration.ofMillis(456);
+
+ config.getStateChangeCallback().onStateChanged(true,
+ (int) MILLISECONDS.toMicros(samplingPeriod.toMillis()),
+ (int) MILLISECONDS.toMicros(batchLatency.toMillis()));
+
+ verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency);
+ }
+}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
new file mode 100644
index 0000000..a9583fd
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorEventTest {
+
+ private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
+ private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
+
+ @Test
+ public void parcelAndUnparcel_matches() {
+ final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+ .setTimestampNanos(TIMESTAMP_NANOS)
+ .build();
+ final Parcel parcel = Parcel.obtain();
+ originalEvent.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ final VirtualSensorEvent recreatedEvent =
+ VirtualSensorEvent.CREATOR.createFromParcel(parcel);
+ assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
+ assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
+ }
+
+ @Test
+ public void sensorEvent_nullValues() {
+ assertThrows(
+ IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
+ }
+
+ @Test
+ public void sensorEvent_noValues() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new VirtualSensorEvent.Builder(new float[0]).build());
+ }
+
+ @Test
+ public void sensorEvent_noTimestamp_usesCurrentTime() {
+ final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
+ assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+ assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
+ assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
+ }
+
+ @Test
+ public void sensorEvent_created() {
+ final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+ .setTimestampNanos(TIMESTAMP_NANOS)
+ .build();
+ assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
+ assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+ }
+}
diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java
index a76c640..caa7312 100644
--- a/core/tests/utiltests/src/android/util/IntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/IntArrayTest.java
@@ -16,8 +16,8 @@
package android.util;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -25,6 +25,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IntArrayTest {
@@ -35,51 +37,65 @@
a.add(1);
a.add(2);
a.add(3);
- verify(new int[]{1, 2, 3}, a);
+ verify(a, 1, 2, 3);
IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3);
a.addAll(b);
- verify(new int[]{1, 2, 3, 4, 5, 6}, a);
+ verify(a, 1, 2, 3, 4, 5, 6);
a.resize(2);
- verify(new int[]{1, 2}, a);
+ verify(a, 1, 2);
a.resize(8);
- verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+ verify(a, 1, 2, 0, 0, 0, 0, 0, 0);
a.set(5, 10);
- verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+ verify(a, 1, 2, 0, 0, 0, 10, 0, 0);
a.add(5, 20);
- assertEquals(20, a.get(5));
- assertEquals(5, a.indexOf(20));
- verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+ assertThat(a.get(5)).isEqualTo(20);
+ assertThat(a.indexOf(20)).isEqualTo(5);
+ verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0);
- assertEquals(-1, a.indexOf(99));
+ assertThat(a.indexOf(99)).isEqualTo(-1);
a.resize(15);
a.set(14, 30);
- verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+ verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30);
int[] backingArray = new int[]{1, 2, 3, 4};
a = IntArray.wrap(backingArray);
a.set(0, 10);
- assertEquals(10, backingArray[0]);
+ assertThat(backingArray[0]).isEqualTo(10);
backingArray[1] = 20;
backingArray[2] = 30;
- verify(backingArray, a);
- assertEquals(2, a.indexOf(30));
+ verify(a, backingArray);
+ assertThat(a.indexOf(30)).isEqualTo(2);
a.resize(2);
- assertEquals(0, backingArray[2]);
- assertEquals(0, backingArray[3]);
+ assertThat(backingArray[2]).isEqualTo(0);
+ assertThat(backingArray[3]).isEqualTo(0);
a.add(50);
- verify(new int[]{10, 20, 50}, a);
+ verify(a, 10, 20, 50);
}
- public void verify(int[] expected, IntArray intArray) {
- assertEquals(expected.length, intArray.size());
- assertArrayEquals(expected, intArray.toArray());
+ @Test
+ public void testToString() {
+ IntArray a = new IntArray(10);
+ a.add(4);
+ a.add(8);
+ a.add(15);
+ a.add(16);
+ a.add(23);
+ a.add(42);
+
+ assertWithMessage("toString()").that(a.toString()).contains("4, 8, 15, 16, 23, 42");
+ assertWithMessage("toString()").that(a.toString()).doesNotContain("0");
+ }
+
+ public void verify(IntArray intArray, int... expected) {
+ assertWithMessage("contents of %s", intArray).that(intArray.toArray()).asList()
+ .containsExactlyElementsIn(Arrays.stream(expected).boxed().toList());
}
}
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 82ced43..0e198d5 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -382,9 +382,9 @@
}
/**
- * Creates a PixelCopy request for the given {@link Window}
+ * Creates a PixelCopy Builder for the given {@link Window}
* @param source The Window to copy from
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofWindow(@NonNull Window source) {
@@ -394,7 +394,7 @@
}
/**
- * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+ * Creates a PixelCopy Builder for the {@link Window} that the given {@link View} is
* attached to.
*
* Note that this copy request is not cropped to the area the View occupies by default.
@@ -404,7 +404,7 @@
*
* @param source A View that {@link View#isAttachedToWindow() is attached} to a window
* that will be used to retrieve the window to copy from.
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofWindow(@NonNull View source) {
@@ -427,10 +427,10 @@
}
/**
- * Creates a PixelCopy request for the given {@link Surface}
+ * Creates a PixelCopy Builder for the given {@link Surface}
*
* @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofSurface(@NonNull Surface source) {
@@ -441,12 +441,12 @@
}
/**
- * Creates a PixelCopy request for the {@link Surface} belonging to the
+ * Creates a PixelCopy Builder for the {@link Surface} belonging to the
* given {@link SurfaceView}
*
* @param source The SurfaceView to copy from. The backing surface must be
* {@link Surface#isValid() valid}
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index a0dde6a..2ec9e8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.animation;
+import android.graphics.Path;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
@@ -53,6 +54,11 @@
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
/**
+ * The default emphasized interpolator. Used for hero / emphasized movement of content.
+ */
+ public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+ /**
* The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
* is disappearing e.g. when moving off screen.
*/
@@ -81,4 +87,14 @@
public static final PathInterpolator DIM_INTERPOLATOR =
new PathInterpolator(.23f, .87f, .52f, -0.11f);
+
+ // Create the default emphasized interpolator
+ private static PathInterpolator createEmphasizedInterpolator() {
+ Path path = new Path();
+ // Doing the same as fast_out_extra_slow_in
+ path.moveTo(0f, 0f);
+ path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+ path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+ return new PathInterpolator(path);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
new file mode 100644
index 0000000..36cf29a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background surface for the back animations
+ */
+public class BackAnimationBackground {
+ private static final int BACKGROUND_LAYER = -1;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private SurfaceControl mBackgroundSurface;
+
+ public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ }
+
+ void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundSurface != null) {
+ return;
+ }
+
+ final float[] colorComponents = new float[] { Color.red(color) / 255.f,
+ Color.green(color) / 255.f, Color.blue(color) / 255.f };
+
+ final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+ .setName("back-animation-background")
+ .setCallsite("BackAnimationBackground")
+ .setColorLayer();
+
+ mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+ mBackgroundSurface = colorLayerBuilder.build();
+ transaction.setColor(mBackgroundSurface, colorComponents)
+ .setLayer(mBackgroundSurface, BACKGROUND_LAYER)
+ .show(mBackgroundSurface);
+ }
+
+ void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundSurface == null) {
+ return;
+ }
+
+ if (mBackgroundSurface.isValid()) {
+ transaction.remove(mBackgroundSurface);
+ }
+ mBackgroundSurface = null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f811940..0133f6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -138,14 +138,18 @@
}
};
+ private final BackAnimationBackground mAnimationBackground;
+
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
- Context context) {
+ Context context,
+ @NonNull BackAnimationBackground backAnimationBackground) {
this(shellInit, shellController, shellExecutor, backgroundHandler,
- ActivityTaskManager.getService(), context, context.getContentResolver());
+ ActivityTaskManager.getService(), context, context.getContentResolver(),
+ backAnimationBackground);
}
@VisibleForTesting
@@ -155,7 +159,8 @@
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver) {
+ Context context, ContentResolver contentResolver,
+ @NonNull BackAnimationBackground backAnimationBackground) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -163,6 +168,7 @@
mContentResolver = contentResolver;
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
+ mAnimationBackground = backAnimationBackground;
}
@VisibleForTesting
@@ -184,10 +190,14 @@
return;
}
- final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+ final CrossTaskBackAnimation crossTaskAnimation =
+ new CrossTaskBackAnimation(mContext, mAnimationBackground);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
- new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
- // TODO (238474994): register cross activity animation when it's completed.
+ crossTaskAnimation.mBackAnimationRunner);
+ final CrossActivityAnimation crossActivityAnimation =
+ new CrossActivityAnimation(mContext, mAnimationBackground);
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ crossActivityAnimation.mBackAnimationRunner);
// TODO (236760237): register dialog close animation when it's completed.
}
@@ -275,7 +285,8 @@
@Override
public void clearBackToLauncherCallback() {
executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
- (controller) -> controller.clearBackToLauncherCallback());
+ (controller) -> controller.unregisterAnimation(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME));
}
@Override
@@ -289,8 +300,8 @@
mAnimationDefinition.set(type, runner);
}
- private void clearBackToLauncherCallback() {
- mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+ mAnimationDefinition.remove(type);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
new file mode 100644
index 0000000..9f6bc5d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class CrossActivityAnimation {
+ /**
+ * Minimum scale of the entering/closing window.
+ */
+ private static final float MIN_WINDOW_SCALE = 0.9f;
+
+ /**
+ * Minimum alpha of the closing/entering window.
+ */
+ private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
+
+ /**
+ * Progress value to fly out closing window and fly in entering window.
+ */
+ private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
+
+ /** Max window translation in the Y axis. */
+ private static final int WINDOW_MAX_DELTA_Y = 160;
+
+ /** Duration of fade in/out entering window. */
+ private static final int FADE_IN_DURATION = 100;
+ /** Duration of post animation after gesture committed. */
+ private static final int POST_ANIMATION_DURATION = 350;
+ private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+
+ private final Rect mStartTaskRect = new Rect();
+ private final float mCornerRadius;
+
+ // The closing window properties.
+ private final RectF mClosingRect = new RectF();
+
+ // The entering window properties.
+ private final Rect mEnteringStartRect = new Rect();
+ private final RectF mEnteringRect = new RectF();
+
+ private float mCurrentAlpha = 1.0f;
+
+ private float mEnteringMargin = 0;
+ private ValueAnimator mEnteringAnimator;
+ private boolean mEnteringWindowShow = false;
+
+ private final PointF mInitialTouchPos = new PointF();
+
+ private final Matrix mTransformMatrix = new Matrix();
+
+ private final float[] mTmpFloat9 = new float[9];
+
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private boolean mBackInProgress = false;
+
+ private PointF mTouchPos = new PointF();
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
+
+ private final BackAnimationBackground mBackground;
+
+ CrossActivityAnimation(Context context, BackAnimationBackground background) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackground = background;
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+
+ private float getInterpolatedProgress(float backProgress) {
+ return INTERPOLATOR.getInterpolation(backProgress);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+ mTransaction.setAnimationTransaction();
+
+ // Offset start rectangle to align task bounds.
+ mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+ mStartTaskRect.offsetTo(0, 0);
+
+ // Draw background with task background color.
+ mBackground.ensureBackground(
+ mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+ }
+
+ private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+ final float scale = targetRect.width() / mStartTaskRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+ mTransaction.setAlpha(leash, targetAlpha)
+ .setMatrix(leash, mTransformMatrix, mTmpFloat9)
+ .setWindowCrop(leash, mStartTaskRect)
+ .setCornerRadius(leash, mCornerRadius);
+ }
+
+ private void finishAnimation() {
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
+ }
+
+ mTransaction.apply();
+ mBackInProgress = false;
+ mTransformMatrix.reset();
+ mInitialTouchPos.set(0, 0);
+ mEnteringWindowShow = false;
+ mEnteringMargin = 0;
+
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ }
+
+ private void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (!mBackInProgress) {
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ mBackInProgress = true;
+ }
+ mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ return;
+ }
+
+ final float progress = getInterpolatedProgress(backEvent.getProgress());
+ final float touchY = mTouchPos.y;
+
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
+
+ final float closingWidth = closingScale * width;
+ final float closingHeight = (float) height / width * closingWidth;
+
+ // Move the window along the X axis.
+ final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+
+ // Move the window along the Y axis.
+ final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+ final float closingTop = (height - closingHeight) * 0.5f + deltaY;
+ mClosingRect.set(
+ closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+ mEnteringRect.set(mClosingRect);
+
+ // Switch closing/entering targets while reach to the threshold progress.
+ if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
+ return;
+ }
+
+ // Present windows and update the alpha.
+ mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
+ mClosingRect.offset(mEnteringMargin, 0);
+ mEnteringRect.offset(mEnteringMargin - width, 0);
+
+ applyTransform(
+ mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
+ applyTransform(
+ mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
+ mTransaction.apply();
+ }
+
+ private boolean showEnteringWindow(boolean show) {
+ if (mEnteringAnimator == null) {
+ mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
+ mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
+ mEnteringAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ final int width = mStartTaskRect.width();
+ mEnteringMargin = width * progress;
+ // We don't animate to 0 or the surface would become invisible and lose focus.
+ final float alpha = progress >= 0.5f ? 0.01f
+ : mapRange(progress * 2, mCurrentAlpha, 0.01f);
+ mClosingRect.offset(mEnteringMargin, 0);
+ mEnteringRect.offset(mEnteringMargin - width, 0);
+
+ applyTransform(mClosingTarget.leash, mClosingRect, alpha);
+ applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
+ mTransaction.apply();
+ });
+ }
+
+ if (mEnteringAnimator.isRunning()) {
+ return true;
+ }
+
+ if (mEnteringWindowShow == show) {
+ return false;
+ }
+
+ mEnteringWindowShow = show;
+ if (show) {
+ mEnteringAnimator.start();
+ } else {
+ mEnteringAnimator.reverse();
+ }
+ return true;
+ }
+
+ private void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ finishAnimation();
+ return;
+ }
+
+ // End the fade in animation.
+ if (mEnteringAnimator.isRunning()) {
+ mEnteringAnimator.cancel();
+ }
+
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase.
+ mEnteringRect.round(mEnteringStartRect);
+ mTransaction.hide(mClosingTarget.leash);
+
+ ValueAnimator valueAnimator =
+ ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
+ valueAnimator.setInterpolator(new DecelerateInterpolator());
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ updatePostCommitEnteringAnimation(progress);
+ mTransaction.apply();
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ private void updatePostCommitEnteringAnimation(float progress) {
+ float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+ float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+ float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+ float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+ float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+
+ mEnteringRect.set(left, top, left + width, top + height);
+ applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
+ }
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossActivityAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ // End the fade in animation.
+ if (mEnteringAnimator.isRunning()) {
+ mEnteringAnimator.cancel();
+ }
+ // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+ mProgressAnimator.reset();
+ finishAnimation();
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ }
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(
+ int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ finishAnimation();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 2074b6a..a9a7b77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -61,7 +61,7 @@
*/
@ShellMainThread
class CrossTaskBackAnimation {
- private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+ private static final int BACKGROUNDCOLOR = 0x43433A;
/**
* Minimum scale of the entering window.
@@ -106,7 +106,6 @@
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
- private SurfaceControl mBackgroundSurface;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mBackInProgress = false;
@@ -115,56 +114,15 @@
private float mProgress = 0;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
-
private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
- final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
- @Override
- public void onBackStarted(BackEvent backEvent) {
- mProgressAnimator.onBackStarted(backEvent,
- CrossTaskBackAnimation.this::onGestureProgress);
- }
+ private final BackAnimationBackground mBackground;
- @Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
- mProgressAnimator.onBackProgressed(backEvent);
- }
-
- @Override
- public void onBackCancelled() {
- mProgressAnimator.reset();
- finishAnimation();
- }
-
- @Override
- public void onBackInvoked() {
- mProgressAnimator.reset();
- onGestureCommitted();
- }
- };
-
- final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
- for (RemoteAnimationTarget a : apps) {
- if (a.mode == MODE_CLOSING) {
- mClosingTarget = a;
- }
- if (a.mode == MODE_OPENING) {
- mEnteringTarget = a;
- }
- }
-
- startBackAnimation();
- mFinishCallback = finishedCallback;
- }
- };
-
- CrossTaskBackAnimation(Context context) {
+ CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackground = background;
}
private float getInterpolatedProgress(float backProgress) {
@@ -182,14 +140,7 @@
mStartTaskRect.offsetTo(0, 0);
// Draw background.
- mBackgroundSurface = new SurfaceControl.Builder()
- .setName("Background of Back Navigation")
- .setColorLayer()
- .setHidden(false)
- .build();
- mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
- .setLayer(mBackgroundSurface, -1);
- mTransaction.apply();
+ mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction);
}
private void updateGestureBackProgress(float progress, BackEvent event) {
@@ -300,11 +251,11 @@
mClosingTarget = null;
}
- if (mBackgroundSurface != null) {
- mBackgroundSurface.release();
- mBackgroundSurface = null;
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
}
+ mTransaction.apply();
mBackInProgress = false;
mTransformMatrix.reset();
mClosingCurrentRect.setEmpty();
@@ -362,4 +313,49 @@
private static float mapRange(float value, float min, float max) {
return min + (value * (max - min));
}
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossTaskBackAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.reset();
+ finishAnimation();
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ };
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+ };
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 962be9d..4ea8a5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -38,6 +38,7 @@
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationBackground;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -93,13 +94,13 @@
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-import java.util.Optional;
-
import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -255,21 +256,30 @@
@WMSingleton
@Provides
+ static BackAnimationBackground provideBackAnimationBackground(
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ return new BackAnimationBackground(rootTaskDisplayAreaOrganizer);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler
+ @ShellBackgroundThread Handler backgroundHandler,
+ BackAnimationBackground backAnimationBackground
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context));
+ backgroundHandler, context, backAnimationBackground));
}
return Optional.empty();
}
+
//
// Bubbles (optional feature)
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..8ba2583 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -179,8 +180,10 @@
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
}
}
@@ -196,6 +199,39 @@
}
};
+ /**
+ * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+ *
+ * This is done to avoid a race condition between the last transaction applied in
+ * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+ * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+ * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+ * the WCT should be the last transaction to finish the animation. However, it may happen that
+ * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+ * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
+ * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
+ * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
+ * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
+ *
+ * To avoid this, we delay the finishResize operation until
+ * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
+ */
+ private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+ if (!shouldSyncPipTransactionWithMenu()) {
+ finishResizeRunnable.run();
+ return;
+ }
+
+ // Delay the finishResize to the next frame
+ Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+ mMainExecutor.execute(finishResizeRunnable);
+ }, null);
+ }
+
+ private boolean shouldSyncPipTransactionWithMenu() {
+ return mPipMenuController.isMenuVisible();
+ }
+
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,7 @@
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1223,7 +1259,7 @@
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1265,7 +1301,7 @@
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa29..485b400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
}
@@ -90,7 +90,13 @@
if (mRemote.asBinder() != null) {
mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
}
- mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+ startTransaction, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -124,7 +130,13 @@
}
};
try {
- mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT =
+ RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().mergeAnimation(
+ transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error merging remote transition.", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9469529..b4e0584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -120,10 +121,10 @@
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
unhandleDeath(remote.asBinder(), finishCallback);
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
mRequestedRemotes.remove(transition);
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
@@ -131,8 +132,14 @@
};
Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
try {
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT =
+ copyIfLocal(startTransaction, remote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
handleDeath(remote.asBinder(), finishCallback);
- remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -145,6 +152,28 @@
return true;
}
+ static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+ IRemoteTransition remote) {
+ // We care more about parceling than local (though they should be the same); so, use
+ // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+ if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+ // No local interface, so binder itself will parcel and thus we don't need to.
+ return t;
+ }
+ // Binder won't be parceling; however, the remotes assume they have their own native
+ // objects (and don't know if caller is local or not), so we need to make a COPY here so
+ // that the remote can clean it up without clearing the original transaction.
+ // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+ final Parcel p = Parcel.obtain();
+ try {
+ t.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@
}
};
try {
- remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 56d51bd..c6935c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -503,6 +503,7 @@
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
onAbort(transitionToken);
+ releaseSurfaces(info);
return;
}
@@ -607,6 +608,15 @@
onFinish(transition, wct, wctCB, false /* abort */);
}
+ /**
+ * Releases an info's animation-surfaces. These don't need to persist and we need to release
+ * them asap so that SF can free memory sooner.
+ */
+ private void releaseSurfaces(@Nullable TransitionInfo info) {
+ if (info == null) return;
+ info.releaseAnimSurfaces();
+ }
+
private void onFinish(IBinder transition,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB,
@@ -645,6 +655,11 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ if (active.mStartT != null) {
+ // Applied by now, so close immediately. Do not set to null yet, though, since nullness
+ // is used later to disambiguate malformed transitions.
+ active.mStartT.close();
+ }
// Merge all relevant transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -664,12 +679,14 @@
fullFinish.apply();
}
// Now perform all the finishes.
+ releaseSurfaces(active.mInfo);
mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(transition, wct, wctCB);
while (activeIdx < mActiveTransitions.size()) {
if (!mActiveTransitions.get(activeIdx).mMerged) break;
ActiveTransition merged = mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ releaseSurfaces(merged.mInfo);
}
// sift through aborted transitions
while (mActiveTransitions.size() > activeIdx
@@ -682,8 +699,9 @@
}
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(active.mToken, true);
+ mObservers.get(i).onTransitionFinished(aborted.mToken, true);
}
+ releaseSurfaces(aborted.mInfo);
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index d75c36c..bee9a90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -110,6 +110,9 @@
@Mock
private ShellController mShellController;
+ @Mock
+ private BackAnimationBackground mAnimationBackground;
+
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
@@ -127,7 +130,7 @@
mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver);
+ mContentResolver, mAnimationBackground);
mController.setEnableUAnimation(true);
mShellInit.init();
mShellExecutor.flushAll();
@@ -239,7 +242,7 @@
mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver);
+ mContentResolver, mAnimationBackground);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -354,6 +357,10 @@
BackNavigationInfo.TYPE_DIALOG_CLOSE};
for (int type: testTypes) {
+ unregisterAnimation(type);
+ }
+
+ for (int type: testTypes) {
final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
@@ -431,6 +438,10 @@
new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
}
+ private void unregisterAnimation(int type) {
+ mController.unregisterAnimation(type);
+ }
+
private static class ResultListener implements RemoteCallback.OnResultListener {
boolean mBackNavigationDone = false;
boolean mTriggerBack = false;
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 529eddd..5acec79 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -101,6 +101,15 @@
public static final String ACTION_FUSED_PROVIDER =
"com.android.location.service.FusedLocationProvider";
+ /**
+ * The action the wrapping service should have in its intent filter to implement the
+ * {@link android.location.LocationManager#GPS_PROVIDER}.
+ *
+ * @hide
+ */
+ public static final String ACTION_GNSS_PROVIDER =
+ "android.location.provider.action.GNSS_PROVIDER";
+
final String mTag;
final @Nullable String mAttributionTag;
final IBinder mBinder;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f3931df..9c5313a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7670,8 +7670,10 @@
* or video calls. This method can be used by voice or video chat applications to select a
* different audio device than the one selected by default by the platform.
* <p>The device selection is expressed as an {@link AudioDeviceInfo} among devices returned by
- * {@link #getAvailableCommunicationDevices()}.
- * The selection is active as long as the requesting application process lives, until
+ * {@link #getAvailableCommunicationDevices()}. Note that only devices in a sink role
+ * (AKA output devices, see {@link AudioDeviceInfo#isSink()}) can be specified. The matching
+ * source device is selected automatically by the platform.
+ * <p>The selection is active as long as the requesting application process lives, until
* {@link #clearCommunicationDevice} is called or until the device is disconnected.
* It is therefore important for applications to clear the request when a call ends or the
* the requesting activity or service is stopped or destroyed.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 8594ba5..f1b1d79 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -34,6 +34,7 @@
cc_defaults {
name: "libandroid_defaults",
+ cpp_std: "gnu++20",
cflags: [
"-Wall",
"-Werror",
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 30d0c35..fe3132e 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -66,9 +66,6 @@
return mFilePath == o.mFilePath && mLocale == o.mLocale && mWeight == o.mWeight &&
mItalic == o.mItalic && mCollectionIndex == o.mCollectionIndex && mAxes == o.mAxes;
}
-
- AFont() = default;
- AFont(const AFont&) = default;
};
struct FontHasher {
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 71c52d9..37d6b42 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -36,7 +36,7 @@
</activity>
<provider
- android:name="com.android.settingslib.spa.framework.SpaSearchProvider"
+ android:name="com.android.settingslib.spa.search.SpaSearchProvider"
android:authorities="com.android.spa.gallery.search.provider"
android:enabled="true"
android:exported="false">
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 6ed7481..cd3ec96 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -16,21 +16,17 @@
package com.android.settingslib.spa.framework
+import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.core.view.WindowCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -39,12 +35,13 @@
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
import com.android.settingslib.spa.framework.compose.localNavController
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.PageEvent
import com.android.settingslib.spa.framework.util.navRoute
private const val TAG = "BrowseActivity"
@@ -77,12 +74,7 @@
setContent {
SettingsTheme {
val sppRepository by spaEnvironment.pageProviderRepository
- BrowseContent(
- allProviders = sppRepository.getAllProviders(),
- initialDestination = intent?.getStringExtra(KEY_DESTINATION)
- ?: sppRepository.getDefaultStartPage(),
- initialEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
- )
+ BrowseContent(sppRepository, intent)
}
}
}
@@ -90,44 +82,18 @@
companion object {
const val KEY_DESTINATION = "spaActivityDestination"
const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+ const val KEY_SESSION_SOURCE_NAME = "sessionSource"
}
}
@VisibleForTesting
@Composable
-fun BrowseContent(
- allProviders: Collection<SettingsPageProvider>,
- initialDestination: String,
- initialEntryId: String?
-) {
+fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
val navController = rememberNavController()
CompositionLocalProvider(navController.localNavController()) {
val controller = LocalNavController.current as NavControllerWrapperImpl
- controller.NavContent(allProviders)
- controller.InitialDestination(initialDestination, initialEntryId)
- }
-}
-
-@Composable
-private fun SettingsPageProvider.PageEvents(arguments: Bundle? = null) {
- val page = remember(arguments) { createSettingsPage(arguments) }
- val lifecycleOwner = LocalLifecycleOwner.current
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- page.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- page.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
+ controller.NavContent(sppRepository.getAllProviders())
+ controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage())
}
}
@@ -144,7 +110,7 @@
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
) { navBackStackEntry ->
- spp.PageEvents(navBackStackEntry.arguments)
+ spp.PageEvent(navBackStackEntry.arguments)
spp.Page(navBackStackEntry.arguments)
}
}
@@ -153,17 +119,23 @@
@Composable
private fun NavControllerWrapperImpl.InitialDestination(
- destination: String,
- highlightEntryId: String?
+ initialIntent: Intent?,
+ defaultDestination: String
) {
val destinationNavigated = rememberSaveable { mutableStateOf(false) }
if (destinationNavigated.value) return
destinationNavigated.value = true
- if (destination.isEmpty()) return
+ val initialDestination = initialIntent?.getStringExtra(BrowseActivity.KEY_DESTINATION)
+ ?: defaultDestination
+ if (initialDestination.isEmpty()) return
+ val initialEntryId = initialIntent?.getStringExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY)
+ val sessionSourceName = initialIntent?.getStringExtra(BrowseActivity.KEY_SESSION_SOURCE_NAME)
+
LaunchedEffect(Unit) {
- highlightId = highlightEntryId
- navController.navigate(destination) {
+ highlightId = initialEntryId
+ sessionName = sessionSourceName
+ navController.navigate(initialDestination) {
popUpTo(navController.graph.findStartDestination().id) {
inclusive = true
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
index 121c07f..61b46be 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.framework.common
import android.content.UriMatcher
+import androidx.annotation.VisibleForTesting
/**
* Enum to define all column names in provider.
@@ -125,14 +126,17 @@
),
}
-internal fun QueryEnum.getColumns(): Array<String> {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getColumns(): Array<String> {
return columnNames.map { it.id }.toTypedArray()
}
-internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getIndex(name: ColumnEnum): Int {
return columnNames.indexOf(name)
}
-internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
uriMatcher.addURI(authority, queryPath, queryMatchCode)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 9ee7f9e..702c075 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -219,11 +219,6 @@
return this
}
- fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder {
- this.isAllowSearch = isAllowSearch
- return this
- }
-
fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder {
this.isSearchDataDynamic = isDynamic
return this
@@ -251,6 +246,13 @@
fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
this.searchDataFn = fn
+ this.isAllowSearch = true
+ return this
+ }
+
+ fun clearSearchDataFn(): SettingsEntryBuilder {
+ this.searchDataFn = { null }
+ this.isAllowSearch = false
return this
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 82e05a5..bc5dca8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -95,24 +95,6 @@
return false
}
- fun enterPage() {
- SpaEnvironmentFactory.instance.logger.event(
- id,
- LogEvent.PAGE_ENTER,
- category = LogCategory.FRAMEWORK,
- details = displayName,
- )
- }
-
- fun leavePage() {
- SpaEnvironmentFactory.instance.logger.event(
- id,
- LogEvent.PAGE_LEAVE,
- category = LogCategory.FRAMEWORK,
- details = displayName,
- )
- }
-
fun createBrowseIntent(entryId: String? = null): Intent? {
val context = SpaEnvironmentFactory.instance.appContext
val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 382c498..eb2bffe 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -29,7 +29,10 @@
fun navigateBack()
val highlightEntryId: String?
- get() = null
+ get() = null
+
+ val sessionSourceName: String?
+ get() = null
}
@Composable
@@ -63,6 +66,7 @@
private val onBackPressedDispatcher: OnBackPressedDispatcher?,
) : NavControllerWrapper {
var highlightId: String? = null
+ var sessionName: String? = null
override fun navigate(route: String) {
navController.navigate(route)
@@ -73,5 +77,8 @@
}
override val highlightEntryId: String?
- get() = highlightId
+ get() = highlightId
+
+ override val sessionSourceName: String?
+ get() = sessionName
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
similarity index 100%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
new file mode 100644
index 0000000..b9e4b78
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.LocalNavController
+
+@Composable
+internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
+ val page = remember(arguments) { createSettingsPage(arguments) }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val navController = LocalNavController.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ val spaLogger = SpaEnvironmentFactory.instance.logger
+ if (event == Lifecycle.Event.ON_START) {
+ spaLogger.event(
+ page.id,
+ LogEvent.PAGE_ENTER,
+ category = LogCategory.FRAMEWORK,
+ details = navController.sessionSourceName ?: page.displayName,
+ )
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ spaLogger.event(
+ page.id,
+ LogEvent.PAGE_LEAVE,
+ category = LogCategory.FRAMEWORK,
+ details = navController.sessionSourceName ?: page.displayName,
+ )
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 3689e4e..7f2f4fd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.search
import android.content.ContentProvider
import android.content.ContentValues
@@ -26,6 +26,7 @@
import android.database.MatrixCursor
import android.net.Uri
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.android.settingslib.spa.framework.common.ColumnEnum
import com.android.settingslib.spa.framework.common.QueryEnum
import com.android.settingslib.spa.framework.common.SettingsEntry
@@ -115,7 +116,8 @@
}
}
- private fun querySearchImmutableStatusData(): Cursor {
+ @VisibleForTesting
+ fun querySearchImmutableStatusData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
@@ -125,7 +127,8 @@
return cursor
}
- private fun querySearchMutableStatusData(): Cursor {
+ @VisibleForTesting
+ fun querySearchMutableStatusData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
@@ -135,7 +138,8 @@
return cursor
}
- private fun querySearchStaticData(): Cursor {
+ @VisibleForTesting
+ fun querySearchStaticData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
@@ -145,7 +149,8 @@
return cursor
}
- private fun querySearchDynamicData(): Cursor {
+ @VisibleForTesting
+ fun querySearchDynamicData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 77c564b..b6099e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.common.EntryMacro
import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.EntryHighlight
@@ -56,10 +55,6 @@
keyword = searchKeywords
)
}
-
- override fun getStatusData(): EntryStatusData {
- return EntryStatusData(isDisabled = false)
- }
}
/**
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index bde3bba..bd5884d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -30,6 +30,7 @@
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth
import org.junit.Rule
@@ -45,7 +46,8 @@
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaLogger = SpaLoggerForTest()
- private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
@Test
fun testBrowsePage() {
@@ -58,13 +60,7 @@
val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
val pageLayer1 = sppLayer1.createSettingsPage()
- composeTestRule.setContent {
- BrowseContent(
- allProviders = listOf(sppHome, sppLayer1),
- initialDestination = pageHome.buildRoute(),
- initialEntryId = null
- )
- }
+ composeTestRule.setContent { BrowseContent(sppRepository) }
composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
spaLogger.verifyPageEvent(pageHome.id, 1, 0)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index f8339b6..934b8f5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -30,7 +30,8 @@
@RunWith(AndroidJUnit4::class)
class SettingsEntryRepositoryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
private val entryRepository by spaEnvironment.entryRepository
@Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 2017d53..a343f6c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -64,6 +64,7 @@
assertThat(entry.isAllowSearch).isFalse()
assertThat(entry.isSearchDataDynamic).isFalse()
assertThat(entry.hasMutableStatus).isFalse()
+ assertThat(entry.hasSliceSupport).isFalse()
}
@Test
@@ -121,12 +122,13 @@
@Test
fun testSetAttributes() {
val owner = SettingsPage.create("mySpp")
- val entry = SettingsEntryBuilder.create(owner, "myEntry")
+ val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
.setDisplayName("myEntryDisplay")
- .setIsAllowSearch(true)
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
- .build()
+ .setSearchDataFn { null }
+ .setSliceDataFn { _, _ -> null }
+ val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntryDisplay")
assertThat(entry.fromPage).isNull()
@@ -134,6 +136,10 @@
assertThat(entry.isAllowSearch).isTrue()
assertThat(entry.isSearchDataDynamic).isFalse()
assertThat(entry.hasMutableStatus).isTrue()
+ assertThat(entry.hasSliceSupport).isTrue()
+
+ val entry2 = entryBuilder.clearSearchDataFn().build()
+ assertThat(entry2.isAllowSearch).isFalse()
}
@Test
@@ -150,6 +156,10 @@
val rtArguments = bundleOf("rtParam" to "v2")
composeTestRule.setContent { entry.UiLayout(rtArguments) }
+ assertThat(entry.isAllowSearch).isTrue()
+ assertThat(entry.isSearchDataDynamic).isFalse()
+ assertThat(entry.hasMutableStatus).isFalse()
+ assertThat(entry.hasSliceSupport).isFalse()
val searchData = entry.getSearchData(rtArguments)
val statusData = entry.getStatusData(rtArguments)
assertThat(searchData?.title).isEqualTo("myTitle")
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 743b5e3..15c2db50 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -24,7 +24,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.tests.testutils.BlankActivity
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,8 +31,7 @@
@RunWith(AndroidJUnit4::class)
class SettingsPageTest {
private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaLogger = SpaLoggerForTest()
- private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+ private val spaEnvironment = SpaEnvironmentForTest(context)
@Test
fun testNullPage() {
@@ -74,10 +72,14 @@
"int_param" to 10,
)
val page = spaEnvironment.createPage("SppWithParam", arguments)
- assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
- navArgument("string_param") { type = NavType.StringType },
- navArgument("int_param") { type = NavType.IntType },
- ), arguments))
+ assertThat(page.id).isEqualTo(
+ getUniquePageId(
+ "SppWithParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ ), arguments
+ )
+ )
assertThat(page.sppName).isEqualTo("SppWithParam")
assertThat(page.displayName).isEqualTo("SppWithParam")
assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
@@ -98,11 +100,15 @@
"rt_param" to "rtStr",
)
val page = spaEnvironment.createPage("SppWithRtParam", arguments)
- assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
- navArgument("string_param") { type = NavType.StringType },
- navArgument("int_param") { type = NavType.IntType },
- navArgument("rt_param") { type = NavType.StringType },
- ), arguments))
+ assertThat(page.id).isEqualTo(
+ getUniquePageId(
+ "SppWithRtParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ ), arguments
+ )
+ )
assertThat(page.sppName).isEqualTo("SppWithRtParam")
assertThat(page.displayName).isEqualTo("SppWithRtParam")
assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
@@ -112,19 +118,4 @@
assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull()
assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull()
}
-
- @Test
- fun testPageEvent() {
- spaLogger.reset()
- SpaEnvironmentFactory.reset(spaEnvironment)
- val page = spaEnvironment.createPage("SppHome")
- page.enterPage()
- page.leavePage()
- page.enterPage()
- assertThat(page.createBrowseIntent()).isNotNull()
- assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
- .isEqualTo(2)
- assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
- .isEqualTo(1)
- }
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
new file mode 100644
index 0000000..cdb0f3a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.search
+
+import android.content.Context
+import android.database.Cursor
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.ColumnEnum
+import com.android.settingslib.spa.framework.common.QueryEnum
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.common.getIndex
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppForSearch
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaSearchProviderTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
+ private val searchProvider = SpaSearchProvider()
+
+ @Test
+ fun testQuerySearchStatusData() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val pageOwner = spaEnvironment.createPage("SppForSearch")
+
+ val immutableStatus = searchProvider.querySearchImmutableStatusData()
+ Truth.assertThat(immutableStatus.count).isEqualTo(1)
+ immutableStatus.moveToFirst()
+ immutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+ )
+
+ val mutableStatus = searchProvider.querySearchMutableStatusData()
+ Truth.assertThat(mutableStatus.count).isEqualTo(2)
+ mutableStatus.moveToFirst()
+ mutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchStaticWithMutableStatus")
+ )
+
+ mutableStatus.moveToNext()
+ mutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+ )
+ }
+
+ @Test
+ fun testQuerySearchIndexData() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val staticData = searchProvider.querySearchStaticData()
+ Truth.assertThat(staticData.count).isEqualTo(2)
+
+ val dynamicData = searchProvider.querySearchDynamicData()
+ Truth.assertThat(dynamicData.count).isEqualTo(2)
+ }
+}
+
+private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
+ Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value)
+}
+
+private fun SettingsPage.getEntryId(name: String): String {
+ return SettingsEntryBuilder.create(this, name).build().id
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 6ebd64f..7fc09ff 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.spa.framework.common.getUniqueEntryId
import com.android.settingslib.spa.testutils.InstantTaskExecutorRule
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer2
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -37,7 +38,8 @@
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
private val sliceDataRepository by spaEnvironment.sliceDataRepository
@Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index ab269f2..6385954 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -24,7 +24,9 @@
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settingslib.spa.framework.BrowseActivity
+import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsEntry
@@ -141,8 +143,43 @@
}
}
+object SppForSearch : SettingsPageProvider {
+ override val name = "SppForSearch"
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SettingsEntryBuilder.create(owner, "SearchStaticWithNoStatus")
+ .setSearchDataFn { EntrySearchData(title = "SearchStaticWithNoStatus") }
+ .build(),
+ SettingsEntryBuilder.create(owner, "SearchStaticWithMutableStatus")
+ .setHasMutableStatus(true)
+ .setSearchDataFn { EntrySearchData(title = "SearchStaticWithMutableStatus") }
+ .setStatusDataFn { EntryStatusData(isSwitchOff = true) }
+ .build(),
+ SettingsEntryBuilder.create(owner, "SearchDynamicWithMutableStatus")
+ .setIsSearchDataDynamic(true)
+ .setHasMutableStatus(true)
+ .setSearchDataFn { EntrySearchData(title = "SearchDynamicWithMutableStatus") }
+ .setStatusDataFn { EntryStatusData(isDisabled = true) }
+ .build(),
+ SettingsEntryBuilder.create(owner, "SearchDynamicWithImmutableStatus")
+ .setIsSearchDataDynamic(true)
+ .setSearchDataFn {
+ EntrySearchData(
+ title = "SearchDynamicWithImmutableStatus",
+ keyword = listOf("kw1", "kw2")
+ )
+ }
+ .setStatusDataFn { EntryStatusData(isDisabled = true) }
+ .build(),
+ )
+ }
+}
+
class SpaEnvironmentForTest(
context: Context,
+ rootPages: List<SettingsPage> = emptyList(),
override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
BlankSliceBroadcastReceiver::class.java,
@@ -153,6 +190,7 @@
SettingsPageProviderRepository(
listOf(
SppHome, SppLayer1, SppLayer2,
+ SppForSearch,
object : SettingsPageProvider {
override val name = "SppWithParam"
override val parameter = listOf(
@@ -169,7 +207,7 @@
)
},
),
- listOf(SettingsPage.create("SppHome"))
+ rootPages
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 681eb1c..15766e1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -55,19 +55,22 @@
val searchQuery: State<String>,
)
+internal data class AppListInput<T : AppRecord>(
+ val config: AppListConfig,
+ val listModel: AppListModel<T>,
+ val state: AppListState,
+ val header: @Composable () -> Unit,
+ val appItem: @Composable AppListItemModel<T>.() -> Unit,
+ val bottomPadding: Dp,
+)
+
/**
* The template to render an App List.
*
* This UI element will take the remaining space on the screen to show the App List.
*/
@Composable
-internal fun <T : AppRecord> AppList(
- config: AppListConfig,
- listModel: AppListModel<T>,
- state: AppListState,
- header: @Composable () -> Unit,
- appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
- bottomPadding: Dp,
+internal fun <T : AppRecord> AppListInput<T>.AppList(
appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
loadAppListData(config, listModel, state)
},
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
index ac3f8ff..28bf832 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -35,16 +35,13 @@
)
@Composable
-fun <T : AppRecord> AppListItem(
- itemModel: AppListItemModel<T>,
- onClick: () -> Unit,
-) {
+fun <T : AppRecord> AppListItemModel<T>.AppListItem(onClick: () -> Unit) {
Preference(remember {
object : PreferenceModel {
- override val title = itemModel.label
- override val summary = itemModel.summary
+ override val title = label
+ override val summary = this@AppListItem.summary
override val icon = @Composable {
- AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize)
+ AppIcon(app = record.app, size = SettingsDimension.appIconItemSize)
}
override val onClick = onClick
}
@@ -58,7 +55,6 @@
val record = object : AppRecord {
override val app = LocalContext.current.applicationInfo
}
- val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState())
- AppListItem(itemModel) {}
+ AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()).AppListItem {}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index f371ce9..d452c74 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -47,7 +47,23 @@
primaryUserOnly: Boolean = false,
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
header: @Composable () -> Unit = {},
- appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ appItem: @Composable AppListItemModel<T>.() -> Unit,
+) {
+ AppListPageImpl(
+ title, listModel, showInstantApps, primaryUserOnly, moreOptions, header, appItem,
+ ) { it.AppList() }
+}
+
+@Composable
+internal fun <T : AppRecord> AppListPageImpl(
+ title: String,
+ listModel: AppListModel<T>,
+ showInstantApps: Boolean = false,
+ primaryUserOnly: Boolean = false,
+ moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
+ header: @Composable () -> Unit = {},
+ appItem: @Composable AppListItemModel<T>.() -> Unit,
+ appList: @Composable (input: AppListInput<T>) -> Unit,
) {
val showSystem = rememberSaveable { mutableStateOf(false) }
SearchScaffold(
@@ -64,7 +80,7 @@
val options = remember { listModel.getSpinnerOptions() }
val selectedOption = rememberSaveable { mutableStateOf(0) }
Spinner(options, selectedOption.value) { selectedOption.value = it }
- AppList(
+ val appListInput = AppListInput(
config = AppListConfig(
userId = userInfo.id,
showInstantApps = showInstantApps,
@@ -79,6 +95,7 @@
appItem = appItem,
bottomPadding = bottomPadding,
)
+ appList(appListInput)
}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index 5290bec..452971b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
@@ -9,8 +9,7 @@
import com.android.settingslib.spaprivileged.model.app.AppRecord
@Composable
-fun <T : AppRecord> AppListSwitchItem(
- itemModel: AppListItemModel<T>,
+fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
onClick: () -> Unit,
checked: State<Boolean?>,
changeable: State<Boolean>,
@@ -19,14 +18,14 @@
TwoTargetSwitchPreference(
model = remember {
object : SwitchPreferenceModel {
- override val title = itemModel.label
- override val summary = itemModel.summary
+ override val title = label
+ override val summary = this@AppListSwitchItem.summary
override val checked = checked
override val changeable = changeable
override val onCheckedChange = onCheckedChange
}
},
- icon = { AppIcon(itemModel.record.app, SettingsDimension.appIconItemSize) },
+ icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
onClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index de5a4a2..8287693 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -66,7 +66,7 @@
val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
- SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
+ SettingsEntryBuilder.create(ENTRY_NAME, owner).build()
)
return entryList
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 6db2733..00eb607 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -70,7 +70,6 @@
entryList.add(
SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
.setLink(toPage = appInfoPage)
- .setIsAllowSearch(false)
.build()
)
}
@@ -92,12 +91,11 @@
AppListPage(
title = stringResource(listModel.pageTitleResId),
listModel = internalListModel,
- ) { itemModel ->
+ ) {
AppListItem(
- itemModel = itemModel,
onClick = TogglePermissionAppInfoPageProvider.navigator(
permissionType = permissionType,
- app = itemModel.record.app,
+ app = record.app,
),
)
}
@@ -120,7 +118,7 @@
parameter = PAGE_PARAMETER,
arguments = bundleOf(PERMISSION to permissionType)
)
- return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
+ return SettingsEntryBuilder.createInject(owner = appListPage)
.setUiLayoutFn {
val listModel = rememberContext(listModelSupplier)
Preference(
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
new file mode 100644
index 0000000..fb1e09a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- Test Permission title. [DO NOT TRANSLATE] -->
+ <string name="test_permission_title" translatable="false">Test Permission</string>
+
+ <!-- Test Permission switch title. [DO NOT TRANSLATE] -->
+ <string name="test_permission_switch_title" translatable="false">Allow Test Permission</string>
+
+ <!-- Test Permission footer. [DO NOT TRANSLATE] -->
+ <string name="test_permission_footer" translatable="false">Test Permission is for demo.</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index bc6925b..c4f2df2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -40,9 +40,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class AppListRepositoryTest {
-
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index b570815..65c547a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -39,8 +39,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class AppListViewModelTest {
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
index 4207490..4002655 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
@@ -40,8 +40,7 @@
@RunWith(AndroidJUnit4::class)
class PackageManagerExtTest {
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
new file mode 100644
index 0000000..c3c96c6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListPageTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun title_isDisplayed() {
+ setContent()
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun appListState_hasCorrectInitialState() {
+ val inputState by setContent()
+
+ val state = inputState!!.state
+ assertThat(state.showSystem.value).isFalse()
+ assertThat(state.option.value).isEqualTo(0)
+ assertThat(state.searchQuery.value).isEqualTo("")
+ }
+
+ @Test
+ fun canShowSystem() {
+ val inputState by setContent()
+
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+ composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+ val state = inputState!!.state
+ assertThat(state.showSystem.value).isTrue()
+ }
+
+ @Test
+ fun afterShowSystem_displayHideSystem() {
+ setContent()
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+ composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun whenHasOptions_firstOptionDisplayed() {
+ val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+ composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
+ composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
+ val state = inputState!!.state
+ assertThat(state.option.value).isEqualTo(0)
+ }
+
+ @Test
+ fun whenHasOptions_couldSwitchOption() {
+ val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+ composeTestRule.onNodeWithText(OPTION_0).performClick()
+ composeTestRule.onNodeWithText(OPTION_1).performClick()
+
+ composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed()
+ composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist()
+ val state = inputState!!.state
+ assertThat(state.option.value).isEqualTo(1)
+ }
+
+ private fun setContent(
+ options: List<String> = emptyList(),
+ header: @Composable () -> Unit = {},
+ ): State<AppListInput<TestAppRecord>?> {
+ val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
+ composeTestRule.setContent {
+ AppListPageImpl(
+ title = TITLE,
+ listModel = TestAppListModel(options),
+ header = header,
+ appItem = { AppListItem {} },
+ appList = { appListState.value = it },
+ )
+ }
+ return appListState
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val OPTION_0 = "Option 1"
+ const val OPTION_1 = "Option 2"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 9f20c78..df80dd4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -29,14 +29,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
-import com.android.settingslib.spaprivileged.model.app.AppListModel
-import com.android.settingslib.spaprivileged.model.app.AppRecord
-import kotlinx.coroutines.flow.Flow
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -92,21 +90,19 @@
enableGrouping: Boolean = false,
) {
composeTestRule.setContent {
- AppList(
+ val appListInput = AppListInput(
config = AppListConfig(userId = USER_ID, showInstantApps = false),
- listModel = TestAppListModel(enableGrouping),
+ listModel = TestAppListModel(enableGrouping = enableGrouping),
state = AppListState(
showSystem = false.toState(),
option = 0.toState(),
searchQuery = "".toState(),
),
header = header,
- appItem = { AppListItem(it) {} },
+ appItem = { AppListItem {} },
bottomPadding = 0.dp,
- appListDataSupplier = {
- stateOf(AppListData(appEntries, option = 0))
- }
)
+ appListInput.AppList { stateOf(AppListData(appEntries, option = 0)) }
}
}
@@ -137,25 +133,3 @@
)
}
}
-
-private data class TestAppRecord(
- override val app: ApplicationInfo,
- val group: String? = null,
-) : AppRecord
-
-private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> {
- override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- appListFlow.asyncMapItem { TestAppRecord(it) }
-
- @Composable
- override fun getSummary(option: Int, record: TestAppRecord) = null
-
- override fun filter(
- userIdFlow: Flow<Int>,
- option: Int,
- recordListFlow: Flow<List<TestAppRecord>>,
- ) = recordListFlow
-
- override fun getGroupTitle(option: Int, record: TestAppRecord) =
- if (enableGrouping) record.group else null
-}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index b3638c2..8e98d8c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -41,8 +41,7 @@
@RunWith(AndroidJUnit4::class)
class AppStorageSizeTest {
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
new file mode 100644
index 0000000..4bc612a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListPageTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun appListInjectEntry_titleDisplayed() {
+ val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
+ TestTogglePermissionAppListModel()
+ }.build()
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ entry.UiLayout()
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun appListRoute() {
+ val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+ assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+ }
+
+ private companion object {
+ const val PERMISSION_TYPE = "test.PERMISSION"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
new file mode 100644
index 0000000..af3189f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun appListInjectEntry_titleDisplayed() {
+ val entry = TestTogglePermissionAppListProvider.buildAppListInjectEntry().build()
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ entry.UiLayout()
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun appListRoute() {
+ val route = TestTogglePermissionAppListProvider.getAppListRoute()
+
+ assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+ }
+
+ @Test
+ fun togglePermissionAppListTemplate_createPageProviders() {
+ val togglePermissionAppListTemplate =
+ TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+ val createPageProviders = togglePermissionAppListTemplate.createPageProviders()
+
+ assertThat(createPageProviders).hasSize(2)
+ assertThat(createPageProviders.any { it is TogglePermissionAppListPageProvider }).isTrue()
+ assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue()
+ }
+}
+
+private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "test.PERMISSION"
+ override fun createModel(context: Context) = TestTogglePermissionAppListModel()
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
new file mode 100644
index 0000000..d556487
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+
+data class TestAppRecord(
+ override val app: ApplicationInfo,
+ val group: String? = null,
+) : AppRecord
+
+class TestAppListModel(
+ private val options: List<String> = emptyList(),
+ private val enableGrouping: Boolean = false,
+) : AppListModel<TestAppRecord> {
+ override fun getSpinnerOptions() = options
+
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.asyncMapItem { TestAppRecord(it) }
+
+ @Composable
+ override fun getSummary(option: Int, record: TestAppRecord) = null
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<TestAppRecord>>,
+ ) = recordListFlow
+
+ override fun getGroupTitle(option: Int, record: TestAppRecord) =
+ if (enableGrouping) record.group else null
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
new file mode 100644
index 0000000..91a9c6b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import kotlinx.coroutines.flow.Flow
+
+class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRecord> {
+ override val pageTitleResId = R.string.test_permission_title
+ override val switchTitleResId = R.string.test_permission_switch_title
+ override val footerResId = R.string.test_permission_footer
+
+ override fun transformItem(app: ApplicationInfo) = TestAppRecord(app = app)
+
+ override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<TestAppRecord>>) =
+ recordListFlow
+
+ @Composable
+ override fun isAllowed(record: TestAppRecord) = stateOf(null)
+
+ override fun isChangeable(record: TestAppRecord) = false
+
+ override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {}
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 3baef4b..e9c6aed 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -23,5 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
+ "com.android.healthconnect",
],
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7c3948a..87354c7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -168,25 +168,14 @@
}
android_library {
- name: "SystemUI-tests",
+ name: "SystemUI-tests-base",
manifest: "tests/AndroidManifest-base.xml",
- additional_manifests: ["tests/AndroidManifest.xml"],
-
resource_dirs: [
"tests/res",
"res-product",
"res-keyguard",
"res",
],
- srcs: [
- "tests/src/**/*.kt",
- "tests/src/**/*.java",
- "src/**/*.kt",
- "src/**/*.java",
- "src/**/I*.aidl",
- ":ReleaseJavaFiles",
- ":SystemUI-tests-utils",
- ],
static_libs: [
"WifiTrackerLib",
"SystemUIAnimationLib",
@@ -225,9 +214,6 @@
"metrics-helper-lib",
"hamcrest-library",
"androidx.test.rules",
- "androidx.test.uiautomator_uiautomator",
- "mockito-target-extended-minus-junit4",
- "androidx.test.ext.junit",
"testables",
"truth-prebuilt",
"monet",
@@ -237,6 +223,27 @@
"LowLightDreamLib",
"motion_tool_lib",
],
+}
+
+android_library {
+ name: "SystemUI-tests",
+ manifest: "tests/AndroidManifest-base.xml",
+ additional_manifests: ["tests/AndroidManifest.xml"],
+ srcs: [
+ "tests/src/**/*.kt",
+ "tests/src/**/*.java",
+ "src/**/*.kt",
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ":ReleaseJavaFiles",
+ ":SystemUI-tests-utils",
+ ],
+ static_libs: [
+ "SystemUI-tests-base",
+ "androidx.test.uiautomator_uiautomator",
+ "mockito-target-extended-minus-junit4",
+ "androidx.test.ext.junit",
+ ],
libs: [
"android.test.runner",
"android.test.base",
@@ -253,6 +260,45 @@
},
}
+android_app {
+ name: "SystemUIRobo-stub",
+ defaults: [
+ "platform_app_defaults",
+ "SystemUI_app_defaults",
+ ],
+ manifest: "tests/AndroidManifest-base.xml",
+ static_libs: [
+ "SystemUI-tests-base",
+ ],
+ aaptflags: [
+ "--extra-packages",
+ "com.android.systemui",
+ ],
+ dont_merge_manifests: true,
+ platform_apis: true,
+ system_ext_specific: true,
+ certificate: "platform",
+ privileged: true,
+ resource_dirs: [],
+}
+
+android_robolectric_test {
+ name: "SystemUiRoboTests",
+ srcs: [
+ "tests/robolectric/src/**/*.kt",
+ "tests/robolectric/src/**/*.java",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth-prebuilt",
+ ],
+ kotlincflags: ["-Xjvm-default=enable"],
+ instrumentation_for: "SystemUIRobo-stub",
+ java_resource_dirs: ["tests/robolectric/config"],
+}
+
// Opt-out config for optimizing the SystemUI target using R8.
// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
// `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index f9c6841..43bfa74 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -320,9 +320,7 @@
counterWallpaper.cleanUp(finishTransaction)
// Release surface references now. This is apparently to free GPU
// memory while doing quick operations (eg. during CTS).
- for (i in info.changes.indices.reversed()) {
- info.changes[i].leash.release()
- }
+ info.releaseAllSurfaces()
for (i in leashMap.size - 1 downTo 0) {
leashMap.valueAt(i).release()
}
@@ -331,6 +329,7 @@
null /* wct */,
finishTransaction
)
+ finishTransaction.close()
} catch (e: RemoteException) {
Log.e(
"ActivityOptionsCompat",
@@ -364,6 +363,9 @@
) {
// TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
// ignore any incoming merges.
+ // Clean up stuff though cuz GC takes too long for benchmark tests.
+ t.close()
+ info.releaseAllSurfaces()
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 93c8073..1b0dacc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -166,15 +166,14 @@
counterLauncher.cleanUp(finishTransaction);
counterWallpaper.cleanUp(finishTransaction);
// Release surface references now. This is apparently to free GPU memory
- // while doing quick operations (eg. during CTS).
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- info.getChanges().get(i).getLeash().release();
- }
+ // before GC would.
+ info.releaseAllSurfaces();
// Don't release here since launcher might still be using them. Instead
// let launcher release them (eg. via RemoteAnimationTargets)
leashMap.clear();
try {
finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+ finishTransaction.close();
} catch (RemoteException e) {
Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ " finished callback", e);
@@ -203,10 +202,13 @@
synchronized (mFinishRunnables) {
finishRunnable = mFinishRunnables.remove(mergeTarget);
}
+ // Since we're not actually animating, release native memory now
+ t.close();
+ info.releaseAllSurfaces();
if (finishRunnable == null) return;
onAnimationCancelled(false /* isKeyguardOccluded */);
finishRunnable.run();
}
};
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d4d3d25..b7e2494 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -126,15 +126,18 @@
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishedCallback) {
- if (!mergeTarget.equals(mToken)) return;
- if (!mRecentsSession.merge(info, t, recents)) return;
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Error merging transition.", e);
+ if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+ try {
+ finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error merging transition.", e);
+ }
+ // commit taskAppeared after merge transition finished.
+ mRecentsSession.commitTasksAppearedIfNeeded(recents);
+ } else {
+ t.close();
+ info.releaseAllSurfaces();
}
- // commit taskAppeared after merge transition finished.
- mRecentsSession.commitTasksAppearedIfNeeded(recents);
}
};
return new RemoteTransition(remote, appThread);
@@ -248,6 +251,8 @@
}
// In this case, we are "returning" to an already running app, so just consume
// the merge and do nothing.
+ info.releaseAllSurfaces();
+ t.close();
return true;
}
final int layer = mInfo.getChanges().size() * 3;
@@ -264,6 +269,8 @@
t.setLayer(targets[i].leash, layer);
}
t.apply();
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
mAppearedTargets = targets;
return true;
}
@@ -380,9 +387,7 @@
}
// Only release the non-local created surface references. The animator is responsible
// for releasing the leashes created by local.
- for (int i = 0; i < mInfo.getChanges().size(); ++i) {
- mInfo.getChanges().get(i).getLeash().release();
- }
+ mInfo.releaseAllSurfaces();
// Reset all members.
mWrapped = null;
mFinishCB = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index c5190e8..ea808eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -135,7 +135,7 @@
mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
}
mActivityTaskManager.stopSystemLockTaskMode();
- mShadeController.collapsePanel(false);
+ mShadeController.collapseShade(false);
if (mTelecomManager != null && mTelecomManager.isInCall()) {
mTelecomManager.showInCallScreen(false);
if (mEmergencyButtonCallback != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index d60cc75..50449b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -52,6 +52,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -183,15 +184,18 @@
private final AccessibilityManager mA11yManager;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final NotificationShadeWindowController mNotificationShadeController;
+ private final ShadeController mShadeController;
private final StatusBarWindowCallback mNotificationShadeCallback;
private boolean mDismissNotificationShadeActionRegistered;
@Inject
public SystemActions(Context context,
NotificationShadeWindowController notificationShadeController,
+ ShadeController shadeController,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Optional<Recents> recentsOptional) {
mContext = context;
+ mShadeController = shadeController;
mRecentsOptional = recentsOptional;
mReceiver = new SystemActionsBroadcastReceiver();
mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -529,9 +533,7 @@
}
private void handleAccessibilityDismissNotificationShade() {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- centralSurfaces -> centralSurfaces.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
}
private void handleDpadUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 0214313..e631816 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -220,6 +220,7 @@
synchronized (mFinishCallbacks) {
if (mFinishCallbacks.remove(transition) == null) return;
}
+ info.releaseAllSurfaces();
Slog.d(TAG, "Finish IRemoteAnimationRunner.");
finishCallback.onTransitionFinished(null /* wct */, null /* t */);
}
@@ -235,6 +236,8 @@
synchronized (mFinishCallbacks) {
origFinishCB = mFinishCallbacks.remove(transition);
}
+ info.releaseAllSurfaces();
+ t.close();
if (origFinishCB == null) {
// already finished (or not started yet), so do nothing.
return;
@@ -423,12 +426,15 @@
t.apply();
mBinder.setOccluded(true /* isOccluded */, true /* animate */);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ info.releaseAllSurfaces();
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
+ t.close();
+ info.releaseAllSurfaces();
}
};
@@ -440,12 +446,15 @@
t.apply();
mBinder.setOccluded(false /* isOccluded */, true /* animate */);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ info.releaseAllSurfaces();
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
+ t.close();
+ info.releaseAllSurfaces();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5ed3ba7..948239a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -870,7 +870,7 @@
@Override
public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
if (launchIsFullScreen) {
- mCentralSurfaces.instantCollapseNotificationPanel();
+ mShadeController.get().instantCollapseShade();
}
mOccludeAnimationPlaying = false;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index b252be1..f7a9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1053,18 +1053,9 @@
rootOverlay!!.add(mediaFrame)
} else {
val targetHost = getHost(newLocation)!!.hostView
- // When adding back to the host, let's make sure to reset the bounds.
- // Usually adding the view will trigger a layout that does this automatically,
- // but we sometimes suppress this.
+ // This will either do a full layout pass and remeasure, or it will bypass
+ // that and directly set the mediaFrame's bounds within the premeasured host.
targetHost.addView(mediaFrame)
- val left = targetHost.paddingLeft
- val top = targetHost.paddingTop
- mediaFrame.setLeftTopRightBottom(
- left,
- top,
- left + currentBounds.width(),
- top + currentBounds.height()
- )
if (mediaFrame.childCount > 0) {
val child = mediaFrame.getChildAt(0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bf3031..4feb984 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -420,7 +420,9 @@
*/
fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
traceSection("MediaViewController#getMeasurementsForState") {
- val viewState = obtainViewState(hostState) ?: return null
+ // measurements should never factor in the squish fraction
+ val viewState =
+ obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null
measurement.measuredWidth = viewState.width
measurement.measuredHeight = viewState.height
return measurement
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 10d31ea..57b256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -84,6 +84,8 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import android.window.WindowContext;
import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -279,6 +281,13 @@
private final ActionIntentExecutor mActionExecutor;
private final UserManager mUserManager;
+ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Predictive Back callback dispatched");
+ }
+ respondToBack();
+ };
+
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
@@ -465,6 +474,10 @@
}
}
+ private void respondToBack() {
+ dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ }
+
/**
* Update resources on configuration change. Reinflate for theme/color changes.
*/
@@ -476,6 +489,26 @@
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+ mScreenshotView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(@NonNull View v) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Registering Predictive Back callback");
+ }
+ mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull View v) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Unregistering Predictive Back callback");
+ }
+ mScreenshotView.findOnBackInvokedDispatcher()
+ .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+ }
+ });
mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
@@ -503,7 +536,7 @@
if (DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
}
- dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ respondToBack();
return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index aa610bd..de9dcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -16,6 +16,9 @@
package com.android.systemui.shade;
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -29,31 +32,32 @@
*/
public interface ShadeController {
- /**
- * Make our window larger and the panel expanded
- */
- void instantExpandNotificationsPanel();
+ /** Make our window larger and the shade expanded */
+ void instantExpandShade();
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels();
+ /** Collapse the shade instantly with no animation. */
+ void instantCollapseShade();
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels(int flags);
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShade();
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShade(int flags);
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShadeForced();
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShadeDelayed();
/**
* Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
- * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}.
+ * dismissing status bar when on {@link StatusBarState#SHADE}.
*/
- void animateCollapsePanels(int flags, boolean force);
-
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels(int flags, boolean force, boolean delayed);
-
- /** See {@link #animateCollapsePanels(int, boolean)}. */
void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
/**
- * If the notifications panel is not fully expanded, collapse it animated.
+ * If the shade is not fully expanded, collapse it animated.
*
* @return Seems to always return false
*/
@@ -77,9 +81,7 @@
*/
void addPostCollapseAction(Runnable action);
- /**
- * Run all of the runnables added by {@link #addPostCollapseAction}.
- */
+ /** Run all of the runnables added by {@link #addPostCollapseAction}. */
void runPostCollapseRunnables();
/**
@@ -87,13 +89,48 @@
*
* @return true if the shade was open, else false
*/
- boolean collapsePanel();
+ boolean collapseShade();
/**
- * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
- * the panel. Post collapse runnables will be executed
+ * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
+ * the shade. Post collapse runnables will be executed
*
* @param animate true to animate the collapse, false for instantaneous collapse
*/
- void collapsePanel(boolean animate);
+ void collapseShade(boolean animate);
+
+ /** Makes shade expanded but not visible. */
+ void makeExpandedInvisible();
+
+ /** Makes shade expanded and visible. */
+ void makeExpandedVisible(boolean force);
+
+ /** Returns whether the shade is expanded and visible. */
+ boolean isExpandedVisible();
+
+ /** Handle status bar touch event. */
+ void onStatusBarTouch(MotionEvent event);
+
+ /** Sets the listener for when the visibility of the shade changes. */
+ void setVisibilityListener(ShadeVisibilityListener listener);
+
+ /** */
+ void setNotificationPresenter(NotificationPresenter presenter);
+
+ /** */
+ void setNotificationShadeWindowViewController(
+ NotificationShadeWindowViewController notificationShadeWindowViewController);
+
+ /** */
+ void setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController);
+
+ /** Listens for shade visibility changes. */
+ interface ShadeVisibilityListener {
+ /** Called when the visibility of the shade changes. */
+ void visibilityChanged(boolean visible);
+
+ /** Called when shade expanded and visible state changed. */
+ void expandedVisibleChanged(boolean expandedVisible);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d783293..807e2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -16,9 +16,12 @@
package com.android.systemui.shade;
+import android.content.ComponentCallbacks2;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
@@ -27,11 +30,12 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
import java.util.ArrayList;
-import java.util.Optional;
import javax.inject.Inject;
@@ -39,68 +43,81 @@
/** An implementation of {@link ShadeController}. */
@SysUISingleton
-public class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl implements ShadeController {
private static final String TAG = "ShadeControllerImpl";
private static final boolean SPEW = false;
- private final CommandQueue mCommandQueue;
- private final StatusBarStateController mStatusBarStateController;
- protected final NotificationShadeWindowController mNotificationShadeWindowController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final int mDisplayId;
- protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+
+ private final CommandQueue mCommandQueue;
+ private final KeyguardStateController mKeyguardStateController;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final StatusBarStateController mStatusBarStateController;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final StatusBarWindowController mStatusBarWindowController;
+
private final Lazy<AssistManager> mAssistManagerLazy;
+ private final Lazy<NotificationGutsManager> mGutsManager;
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ private boolean mExpandedVisible;
+
+ private NotificationPanelViewController mNotificationPanelViewController;
+ private NotificationPresenter mPresenter;
+ private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private ShadeVisibilityListener mShadeVisibilityListener;
+
@Inject
public ShadeControllerImpl(
CommandQueue commandQueue,
+ KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
- NotificationShadeWindowController notificationShadeWindowController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ StatusBarWindowController statusBarWindowController,
+ NotificationShadeWindowController notificationShadeWindowController,
WindowManager windowManager,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- Lazy<AssistManager> assistManagerLazy
+ Lazy<AssistManager> assistManagerLazy,
+ Lazy<NotificationGutsManager> gutsManager
) {
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
+ mStatusBarWindowController = statusBarWindowController;
+ mGutsManager = gutsManager;
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
- // TODO: Remove circular reference to CentralSurfaces when possible.
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ mKeyguardStateController = keyguardStateController;
mAssistManagerLazy = assistManagerLazy;
}
@Override
- public void instantExpandNotificationsPanel() {
+ public void instantExpandShade() {
// Make our window larger and the panel expanded.
- getCentralSurfaces().makeExpandedVisible(true /* force */);
- getNotificationPanelViewController().expand(false /* animate */);
+ makeExpandedVisible(true /* force */);
+ mNotificationPanelViewController.expand(false /* animate */);
mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
}
@Override
- public void animateCollapsePanels() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ public void animateCollapseShade() {
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
}
@Override
- public void animateCollapsePanels(int flags) {
- animateCollapsePanels(flags, false /* force */, false /* delayed */,
- 1.0f /* speedUpFactor */);
+ public void animateCollapseShade(int flags) {
+ animateCollapsePanels(flags, false, false, 1.0f);
}
@Override
- public void animateCollapsePanels(int flags, boolean force) {
- animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+ public void animateCollapseShadeForced() {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
}
@Override
- public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
- animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+ public void animateCollapseShadeDelayed() {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
}
@Override
@@ -111,34 +128,26 @@
return;
}
if (SPEW) {
- Log.d(TAG, "animateCollapse():"
- + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible()
- + " flags=" + flags);
+ Log.d(TAG,
+ "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
}
-
- // TODO(b/62444020): remove when this bug is fixed
- Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
- + " canPanelBeCollapsed(): "
- + getNotificationPanelViewController().canPanelBeCollapsed());
if (getNotificationShadeWindowView() != null
- && getNotificationPanelViewController().canPanelBeCollapsed()
+ && mNotificationPanelViewController.canPanelBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
// release focus immediately to kick off focus change transition
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
- getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper();
- getNotificationPanelViewController()
- .collapsePanel(true /* animate */, delayed, speedUpFactor);
+ mNotificationShadeWindowViewController.cancelExpandHelper();
+ mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
}
}
-
@Override
public boolean closeShadeIfOpen() {
- if (!getNotificationPanelViewController().isFullyCollapsed()) {
+ if (!mNotificationPanelViewController.isFullyCollapsed()) {
mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
- getCentralSurfaces().visibilityChanged(false);
+ notifyVisibilityChanged(false);
mAssistManagerLazy.get().hideAssist();
}
return false;
@@ -146,21 +155,19 @@
@Override
public boolean isShadeOpen() {
- NotificationPanelViewController controller =
- getNotificationPanelViewController();
- return controller.isExpanding() || controller.isFullyExpanded();
+ return mNotificationPanelViewController.isExpanding()
+ || mNotificationPanelViewController.isFullyExpanded();
}
@Override
public void postOnShadeExpanded(Runnable executable) {
- getNotificationPanelViewController().addOnGlobalLayoutListener(
+ mNotificationPanelViewController.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- if (getCentralSurfaces().getNotificationShadeWindowView()
- .isVisibleToUser()) {
- getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
- getNotificationPanelViewController().postToView(executable);
+ if (getNotificationShadeWindowView().isVisibleToUser()) {
+ mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
+ mNotificationPanelViewController.postToView(executable);
}
}
});
@@ -183,12 +190,11 @@
}
@Override
- public boolean collapsePanel() {
- if (!getNotificationPanelViewController().isFullyCollapsed()) {
+ public boolean collapseShade() {
+ if (!mNotificationPanelViewController.isFullyCollapsed()) {
// close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- getCentralSurfaces().visibilityChanged(false);
+ animateCollapseShadeDelayed();
+ notifyVisibilityChanged(false);
return true;
} else {
@@ -197,33 +203,131 @@
}
@Override
- public void collapsePanel(boolean animate) {
+ public void collapseShade(boolean animate) {
if (animate) {
- boolean willCollapse = collapsePanel();
+ boolean willCollapse = collapseShade();
if (!willCollapse) {
runPostCollapseRunnables();
}
- } else if (!getPresenter().isPresenterFullyCollapsed()) {
- getCentralSurfaces().instantCollapseNotificationPanel();
- getCentralSurfaces().visibilityChanged(false);
+ } else if (!mPresenter.isPresenterFullyCollapsed()) {
+ instantCollapseShade();
+ notifyVisibilityChanged(false);
} else {
runPostCollapseRunnables();
}
}
- private CentralSurfaces getCentralSurfaces() {
- return mCentralSurfacesOptionalLazy.get().get();
+ @Override
+ public void onStatusBarTouch(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (mExpandedVisible) {
+ animateCollapseShade();
+ }
+ }
}
- private NotificationPresenter getPresenter() {
- return getCentralSurfaces().getPresenter();
+ @Override
+ public void instantCollapseShade() {
+ mNotificationPanelViewController.instantCollapse();
+ runPostCollapseRunnables();
}
- protected NotificationShadeWindowView getNotificationShadeWindowView() {
- return getCentralSurfaces().getNotificationShadeWindowView();
+ @Override
+ public void makeExpandedVisible(boolean force) {
+ if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+ if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+ return;
+ }
+
+ mExpandedVisible = true;
+
+ // Expand the window to encompass the full screen in anticipation of the drag.
+ // It's only possible to do atomically because the status bar is at the top of the screen!
+ mNotificationShadeWindowController.setPanelVisible(true);
+
+ notifyVisibilityChanged(true);
+ mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+ notifyExpandedVisibleChanged(true);
}
- private NotificationPanelViewController getNotificationPanelViewController() {
- return getCentralSurfaces().getNotificationPanelViewController();
+ @Override
+ public void makeExpandedInvisible() {
+ if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
+
+ if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
+ return;
+ }
+
+ // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
+ mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+
+ mNotificationPanelViewController.closeQs();
+
+ mExpandedVisible = false;
+ notifyVisibilityChanged(false);
+
+ // Update the visibility of notification shade and status bar window.
+ mNotificationShadeWindowController.setPanelVisible(false);
+ mStatusBarWindowController.setForceStatusBarVisible(false);
+
+ // Close any guts that might be visible
+ mGutsManager.get().closeAndSaveGuts(
+ true /* removeLeavebehind */,
+ true /* force */,
+ true /* removeControls */,
+ -1 /* x */,
+ -1 /* y */,
+ true /* resetMenu */);
+
+ runPostCollapseRunnables();
+ notifyExpandedVisibleChanged(false);
+ mCommandQueue.recomputeDisableFlags(
+ mDisplayId,
+ mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+
+ // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+ // the bouncer appear animation.
+ if (!mKeyguardStateController.isShowing()) {
+ WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+ }
+ }
+
+ @Override
+ public boolean isExpandedVisible() {
+ return mExpandedVisible;
+ }
+
+ @Override
+ public void setVisibilityListener(ShadeVisibilityListener listener) {
+ mShadeVisibilityListener = listener;
+ }
+
+ private void notifyVisibilityChanged(boolean visible) {
+ mShadeVisibilityListener.visibilityChanged(visible);
+ }
+
+ private void notifyExpandedVisibleChanged(boolean expandedVisible) {
+ mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
+ }
+
+ @Override
+ public void setNotificationPresenter(NotificationPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ @Override
+ public void setNotificationShadeWindowViewController(
+ NotificationShadeWindowViewController controller) {
+ mNotificationShadeWindowViewController = controller;
+ }
+
+ private NotificationShadeWindowView getNotificationShadeWindowView() {
+ return mNotificationShadeWindowViewController.getView();
+ }
+
+ @Override
+ public void setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController) {
+ mNotificationPanelViewController = notificationPanelViewController;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 143c697..bd5b8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -94,7 +94,11 @@
private val views: Sequence<View>
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
- private var showingListener: ShowingListener? = null
+ var showingListener: ShowingListener? = null
+ set(value) {
+ field = value
+ }
+ get() = field
init {
contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
@@ -147,10 +151,6 @@
return uiExecutor
}
- fun setShowingListener(l: ShowingListener?) {
- showingListener = l
- }
-
@UiThread
fun setNewRotation(rot: Int) {
dlog("updateRotation: $rot")
@@ -219,7 +219,7 @@
// Update the gravity and margins of the privacy views
@UiThread
- private fun updateRotations(rotation: Int, paddingTop: Int) {
+ open fun updateRotations(rotation: Int, paddingTop: Int) {
// To keep a view in the corner, its gravity is always the description of its current corner
// Therefore, just figure out which view is in which corner. This turns out to be something
// like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
@@ -250,7 +250,7 @@
}
@UiThread
- private fun setCornerSizes(state: ViewState) {
+ open fun setCornerSizes(state: ViewState) {
// StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
// in every rotation. The only thing we need to check is rtl
val rtl = state.layoutRtl
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 64f87ca..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,8 +54,6 @@
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import java.util.Collections;
-
import javax.inject.Inject;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 0ce9656..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -154,7 +154,7 @@
// If the user selected Priority and the previous selection was not priority, show a
// People Tile add request.
if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
}
mGutsContainer.closeControls(v, /* save= */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 073bd4b..b519aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1401,10 +1401,10 @@
mExpandedHeight = height;
setIsExpanded(height > 0);
int minExpansionHeight = getMinExpansionHeight();
- if (height < minExpansionHeight) {
+ if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) {
mClipRect.left = 0;
mClipRect.right = getWidth();
- mClipRect.top = getNotificationsClippingTopBound();
+ mClipRect.top = 0;
mClipRect.bottom = (int) height;
height = minExpansionHeight;
setRequestedClipBounds(mClipRect);
@@ -1466,17 +1466,6 @@
notifyAppearChangedListeners();
}
- private int getNotificationsClippingTopBound() {
- if (isHeadsUpTransition()) {
- // HUN in split shade can go higher than bottom of NSSL when swiping up so we want
- // to give it extra clipping margin. Because clipping has rounded corners, we also
- // need to account for that corner clipping.
- return -mAmbientState.getStackTopMargin() - mCornerRadius;
- } else {
- return 0;
- }
- }
-
private void notifyAppearChangedListeners() {
float appear;
float expandAmount;
@@ -4236,7 +4225,7 @@
mShadeNeedsToClose = false;
postDelayed(
() -> {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
},
DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
}
@@ -5139,6 +5128,7 @@
println(pw, "intrinsicPadding", mIntrinsicPadding);
println(pw, "topPadding", mTopPadding);
println(pw, "bottomPadding", mBottomPadding);
+ mNotificationStackSizeCalculator.dump(pw, args);
});
pw.println();
pw.println("Contents:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index ae854e2..25f99c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.Compile
import com.android.systemui.util.children
+import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@@ -53,6 +54,8 @@
@Main private val resources: Resources
) {
+ private lateinit var lastComputeHeightLog : String
+
/**
* Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
* If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -114,7 +117,9 @@
shelfIntrinsicHeight: Float
): Int {
log { "\n" }
- val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+
+ val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+ /* computeHeight= */ false)
var maxNotifications =
stackHeightSequence.lastIndexWhile { heightResult ->
@@ -157,18 +162,21 @@
shelfIntrinsicHeight: Float
): Float {
log { "\n" }
+ lastComputeHeightLog = ""
val heightPerMaxNotifications =
- computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+ computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+ /* computeHeight= */ true)
val (notificationsHeight, shelfHeightWithSpaceBefore) =
heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
heightPerMaxNotifications.last() // Height with all notifications visible.
}
- log {
- "computeHeight(maxNotifications=$maxNotifications," +
+ lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
"shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
"${notificationsHeight + shelfHeightWithSpaceBefore}" +
" = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
+ log {
+ lastComputeHeightLog
}
return notificationsHeight + shelfHeightWithSpaceBefore
}
@@ -184,7 +192,8 @@
private fun computeHeightPerNotificationLimit(
stack: NotificationStackScrollLayout,
- shelfHeight: Float
+ shelfHeight: Float,
+ computeHeight: Boolean
): Sequence<StackHeight> = sequence {
log { "computeHeightPerNotificationLimit" }
@@ -213,9 +222,14 @@
currentIndex = firstViewInShelfIndex)
spaceBeforeShelf + shelfHeight
}
+
+ val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
+ "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+ if (computeHeight) {
+ lastComputeHeightLog += "\n" + currentLog
+ }
log {
- "i=$i notificationsHeight=$notifications " +
- "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+ currentLog
}
yield(
StackHeight(
@@ -260,6 +274,10 @@
return size
}
+ fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+ }
+
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
if (visibility == GONE || hasNoContentHeight()) return false
if (onLockscreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 01ca667..0ec7c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -193,8 +193,6 @@
void animateExpandSettingsPanel(@Nullable String subpanel);
- void animateCollapsePanels(int flags, boolean force);
-
void collapsePanelOnMainThread();
void togglePanel();
@@ -282,8 +280,6 @@
void postAnimateOpenPanels();
- boolean isExpandedVisible();
-
boolean isPanelExpanded();
void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
@@ -495,12 +491,13 @@
void updateNotificationPanelTouchState();
+ /**
+ * TODO(b/257041702) delete this
+ * @deprecated Use ShadeController#makeExpandedVisible
+ */
+ @Deprecated
void makeExpandedVisible(boolean force);
- void instantCollapseNotificationPanel();
-
- void visibilityChanged(boolean visible);
-
int getDisplayId();
int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index f3482f4..6b72e96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -209,7 +209,7 @@
public void animateExpandNotificationsPanel() {
if (CentralSurfaces.SPEW) {
Log.d(CentralSurfaces.TAG,
- "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+ "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
if (!mCommandQueue.panelsEnabled()) {
return;
@@ -222,7 +222,7 @@
public void animateExpandSettingsPanel(@Nullable String subPanel) {
if (CentralSurfaces.SPEW) {
Log.d(CentralSurfaces.TAG,
- "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+ "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
if (!mCommandQueue.panelsEnabled()) {
return;
@@ -276,7 +276,7 @@
if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -293,7 +293,7 @@
if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
mCentralSurfaces.updateQsExpansionEnabled();
if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -550,7 +550,7 @@
@Override
public void togglePanel() {
if (mCentralSurfaces.isPanelExpanded()) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
} else {
animateExpandNotificationsPanel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 32ea8d6..d988772 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -58,7 +58,6 @@
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -406,12 +405,6 @@
/** */
@Override
- public void animateCollapsePanels(int flags, boolean force) {
- mCommandQueueCallbacks.animateCollapsePanels(flags, force);
- }
-
- /** */
- @Override
public void togglePanel() {
mCommandQueueCallbacks.togglePanel();
}
@@ -493,8 +486,6 @@
private View mReportRejectedTouch;
- private boolean mExpandedVisible;
-
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -893,6 +884,8 @@
updateDisplaySize();
mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
+ initShadeVisibilityListener();
+
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -1083,6 +1076,25 @@
requestTopUi, componentTag))));
}
+ @VisibleForTesting
+ void initShadeVisibilityListener() {
+ mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
+ @Override
+ public void visibilityChanged(boolean visible) {
+ onShadeVisibilityChanged(visible);
+ }
+
+ @Override
+ public void expandedVisibleChanged(boolean expandedVisible) {
+ if (expandedVisible) {
+ setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
+ } else {
+ onExpandedInvisible();
+ }
+ }
+ });
+ }
+
private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
Trace.beginSection("CentralSurfaces#onFoldedStateChanged");
onFoldedStateChangedInternal(isFolded, willGoToSleep);
@@ -1228,7 +1240,7 @@
mNotificationPanelViewController.initDependencies(
this,
- this::makeExpandedInvisible,
+ mShadeController::makeExpandedInvisible,
mNotificationShelfController);
BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1431,6 +1443,7 @@
mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+ mShadeController.setNotificationPresenter(mPresenter);
mNotificationsController.initialize(
this,
mPresenter,
@@ -1480,11 +1493,7 @@
return (v, event) -> {
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
- if (event.getAction() == MotionEvent.ACTION_UP) {
- if (mExpandedVisible) {
- mShadeController.animateCollapsePanels();
- }
- }
+ mShadeController.onStatusBarTouch(event);
return mNotificationShadeWindowView.onTouchEvent(event);
};
}
@@ -1506,6 +1515,9 @@
mNotificationShadeWindowViewController.setupExpandedStatusBar();
mNotificationPanelViewController =
mCentralSurfacesComponent.getNotificationPanelViewController();
+ mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+ mShadeController.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
mCentralSurfacesComponent.getLockIconViewController().init();
mStackScrollerController =
mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
@@ -1827,7 +1839,7 @@
&& isLaunchForActivity) {
onClosingFinished();
} else {
- mShadeController.collapsePanel(true /* animate */);
+ mShadeController.collapseShade(true /* animate */);
}
}
@@ -1838,7 +1850,7 @@
onClosingFinished();
}
if (launchIsFullScreen) {
- instantCollapseNotificationPanel();
+ mShadeController.instantCollapseShade();
}
}
@@ -1928,33 +1940,13 @@
}
@Override
- public void makeExpandedVisible(boolean force) {
- if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
- if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
- return;
- }
-
- mExpandedVisible = true;
-
- // Expand the window to encompass the full screen in anticipation of the drag.
- // This is only possible to do atomically because the status bar is at the top of the screen!
- mNotificationShadeWindowController.setPanelVisible(true);
-
- visibilityChanged(true);
- mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
- }
-
- @Override
public void postAnimateCollapsePanels() {
- mMainExecutor.execute(mShadeController::animateCollapsePanels);
+ mMainExecutor.execute(mShadeController::animateCollapseShade);
}
@Override
public void postAnimateForceCollapsePanels() {
- mMainExecutor.execute(
- () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
- true /* force */));
+ mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
}
@Override
@@ -1963,11 +1955,6 @@
}
@Override
- public boolean isExpandedVisible() {
- return mExpandedVisible;
- }
-
- @Override
public boolean isPanelExpanded() {
return mPanelExpanded;
}
@@ -1996,46 +1983,13 @@
}
}
- void makeExpandedInvisible() {
- if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
-
- if (!mExpandedVisible || mNotificationShadeWindowView == null) {
- return;
- }
-
- // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
- 1.0f /* speedUpFactor */);
-
- mNotificationPanelViewController.closeQs();
-
- mExpandedVisible = false;
- visibilityChanged(false);
-
- // Update the visibility of notification shade and status bar window.
- mNotificationShadeWindowController.setPanelVisible(false);
- mStatusBarWindowController.setForceStatusBarVisible(false);
-
- // Close any guts that might be visible
- mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-
- mShadeController.runPostCollapseRunnables();
+ private void onExpandedInvisible() {
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
showBouncerOrLockScreenIfKeyguard();
} else if (DEBUG) {
Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
}
- mCommandQueue.recomputeDisableFlags(
- mDisplayId,
- mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
-
- // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
- // the bouncer appear animation.
- if (!mKeyguardStateController.isShowing()) {
- WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- }
}
/** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@@ -2072,7 +2026,8 @@
final boolean upOrCancel =
event.getAction() == MotionEvent.ACTION_UP ||
event.getAction() == MotionEvent.ACTION_CANCEL;
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
+ setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
+ !upOrCancel || mShadeController.isExpandedVisible());
}
}
@@ -2221,7 +2176,7 @@
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
synchronized (mQueueLock) {
pw.println("Current Status Bar state:");
- pw.println(" mExpandedVisible=" + mExpandedVisible);
+ pw.println(" mExpandedVisible=" + mShadeController.isExpandedVisible());
pw.println(" mDisplayMetrics=" + mDisplayMetrics);
pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller));
pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)
@@ -2536,10 +2491,8 @@
}
}
if (dismissShade) {
- if (mExpandedVisible && !mBouncerShowing) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed*/);
+ if (mShadeController.isExpandedVisible() && !mBouncerShowing) {
+ mShadeController.animateCollapseShadeDelayed();
} else {
// Do it after DismissAction has been processed to conserve the needed
// ordering.
@@ -2581,7 +2534,7 @@
flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
}
}
- mShadeController.animateCollapsePanels(flags);
+ mShadeController.animateCollapseShade(flags);
}
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
if (mNotificationShadeWindowController != null) {
@@ -2696,10 +2649,9 @@
com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
}
- // Visibility reporting
protected void handleVisibleToUserChanged(boolean visibleToUser) {
if (visibleToUser) {
- handleVisibleToUserChangedImpl(visibleToUser);
+ onVisibleToUser();
mNotificationLogger.startNotificationLogging();
if (!mIsBackCallbackRegistered) {
@@ -2716,7 +2668,7 @@
}
} else {
mNotificationLogger.stopNotificationLogging();
- handleVisibleToUserChangedImpl(visibleToUser);
+ onInvisibleToUser();
if (mIsBackCallbackRegistered) {
ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -2736,41 +2688,38 @@
}
}
- // Visibility reporting
- void handleVisibleToUserChangedImpl(boolean visibleToUser) {
- if (visibleToUser) {
- /* The LEDs are turned off when the notification panel is shown, even just a little bit.
- * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
- * this.
- */
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !mPresenter.isPresenterFullyCollapsed() &&
- (mState == StatusBarState.SHADE
- || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationsController.getActiveNotificationsCount();
- if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
- notificationLoad = 1;
- }
- final int finalNotificationLoad = notificationLoad;
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelRevealed(clearNotificationEffects,
- finalNotificationLoad);
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
- } else {
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelHidden();
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
+ void onVisibleToUser() {
+ /* The LEDs are turned off when the notification panel is shown, even just a little bit.
+ * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
+ * this.
+ */
+ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+ boolean clearNotificationEffects =
+ !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
+ || mState == StatusBarState.SHADE_LOCKED);
+ int notificationLoad = mNotificationsController.getActiveNotificationsCount();
+ if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
+ notificationLoad = 1;
}
+ final int finalNotificationLoad = notificationLoad;
+ mUiBgExecutor.execute(() -> {
+ try {
+ mBarService.onPanelRevealed(clearNotificationEffects,
+ finalNotificationLoad);
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
+ }
+ void onInvisibleToUser() {
+ mUiBgExecutor.execute(() -> {
+ try {
+ mBarService.onPanelHidden();
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
}
private void logStateToEventlog() {
@@ -2948,7 +2897,7 @@
private void updatePanelExpansionForKeyguard() {
if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
- mShadeController.instantExpandNotificationsPanel();
+ mShadeController.instantExpandShade();
}
}
@@ -3067,7 +3016,7 @@
// too heavy for the CPU and GPU on any device.
mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
} else if (!mNotificationPanelViewController.isCollapsing()) {
- instantCollapseNotificationPanel();
+ mShadeController.instantCollapseShade();
}
// Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
@@ -3225,8 +3174,7 @@
@Override
public boolean onMenuPressed() {
if (shouldUnlockOnMenuPressed()) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+ mShadeController.animateCollapseShadeForced();
return true;
}
return false;
@@ -3271,7 +3219,7 @@
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
&& !isBouncerShowingOverDream()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
return true;
}
@@ -3281,8 +3229,7 @@
@Override
public boolean onSpacePressed() {
if (mDeviceInteractive && mState != StatusBarState.SHADE) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+ mShadeController.animateCollapseShadeForced();
return true;
}
return false;
@@ -3322,12 +3269,6 @@
}
}
- @Override
- public void instantCollapseNotificationPanel() {
- mNotificationPanelViewController.instantCollapse();
- mShadeController.runPostCollapseRunnables();
- }
-
/**
* Collapse the panel directly if we are on the main thread, post the collapsing on the main
* thread if we are not.
@@ -3335,9 +3276,9 @@
@Override
public void collapsePanelOnMainThread() {
if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
- mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+ mContext.getMainExecutor().execute(mShadeController::collapseShade);
}
}
@@ -3477,7 +3418,7 @@
mNotificationShadeWindowViewController.cancelCurrentTouch();
}
if (mPanelExpanded && mState == StatusBarState.SHADE) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -3540,7 +3481,7 @@
// The unlocked screen off and fold to aod animations might use our LightRevealScrim -
// we need to be expanded for it to be visible.
if (mDozeParameters.shouldShowLightRevealScrim()) {
- makeExpandedVisible(true);
+ mShadeController.makeExpandedVisible(true);
}
DejankUtils.stopDetectingBlockingIpcs(tag);
@@ -3569,7 +3510,7 @@
// If we are waking up during the screen off animation, we should undo making the
// expanded visible (we did that so the LightRevealScrim would be visible).
if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
- makeExpandedInvisible();
+ mShadeController.makeExpandedInvisible();
}
});
@@ -3624,6 +3565,12 @@
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
+ //TODO(b/257041702) delete
+ @Override
+ public void makeExpandedVisible(boolean force) {
+ mShadeController.makeExpandedVisible(force);
+ }
+
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn(Runnable onDrawn) {
@@ -3904,8 +3851,7 @@
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
if (BANNER_ACTION_SETUP.equals(action)) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
+ mShadeController.animateCollapseShadeForced();
mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -3967,7 +3913,7 @@
action.run();
}).start();
- return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+ return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
}
@Override
@@ -4062,8 +4008,7 @@
mMainExecutor.execute(runnable);
}
- @Override
- public void visibilityChanged(boolean visible) {
+ private void onShadeVisibilityChanged(boolean visible) {
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 44ad604..f9d316b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -469,6 +469,9 @@
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
return;
+ } else if (mKeyguardStateController.isOccluded()
+ && !mDreamOverlayStateController.isOverlayActive()) {
+ return;
} else if (needsFullscreenBouncer()) {
if (mPrimaryBouncer != null) {
mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b6ae4a0..05bf860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -260,11 +260,11 @@
if (showOverLockscreen) {
mShadeController.addPostCollapseAction(runnable);
- mShadeController.collapsePanel(true /* animate */);
+ mShadeController.collapseShade(true /* animate */);
} else if (mKeyguardStateController.isShowing()
&& mCentralSurfaces.isOccluded()) {
mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
runnable.run();
}
@@ -406,7 +406,7 @@
private void expandBubbleStack(NotificationEntry entry) {
mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
}
private void startNotificationIntent(
@@ -593,9 +593,9 @@
private void collapseOnMainThread() {
if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
- mMainThreadHandler.post(mShadeController::collapsePanel);
+ mMainThreadHandler.post(mShadeController::collapseShade);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8a49850..7fe01825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -180,7 +180,7 @@
}
};
mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
- mShadeController.instantExpandNotificationsPanel();
+ mShadeController.instantExpandShade();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index ad97ef4..5df4a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -29,6 +29,7 @@
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
@@ -102,6 +103,15 @@
}
@Override
+ public Looper onProvideEngineLooper() {
+ // Receive messages on mWorker thread instead of SystemUI's main handler.
+ // All other wallpapers have their own process, and they can receive messages on their own
+ // main handler without any delay. But since ImageWallpaper lives in SystemUI, performance
+ // of the image wallpaper could be negatively affected when SystemUI's main handler is busy.
+ return mWorker != null ? mWorker.getLooper() : super.onProvideEngineLooper();
+ }
+
+ @Override
public void onCreate() {
super.onCreate();
mWorker = new HandlerThread(TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a4384d5..7033ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -549,7 +549,7 @@
} catch (RemoteException e) {
Log.e(TAG, e.getMessage());
}
- mShadeController.collapsePanel(true);
+ mShadeController.collapseShade(true);
if (entry.getRow() != null) {
entry.getRow().updateBubbleButton();
}
@@ -597,7 +597,7 @@
}
if (shouldBubble) {
- mShadeController.collapsePanel(true);
+ mShadeController.collapseShade(true);
if (entry.getRow() != null) {
entry.getRow().updateBubbleButton();
}
diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..2a75bd9
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties
@@ -0,0 +1,16 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
new file mode 100644
index 0000000..188dff2
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.robotests;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import static com.google.common.truth.Truth.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiResourceLoadingTest extends SysuiRoboBase {
+ @Test
+ public void testResources() {
+ assertThat(getContext().getString(com.android.systemui.R.string.app_label))
+ .isEqualTo("System UI");
+ assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content))
+ .isNotEmpty();
+ }
+}
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
new file mode 100644
index 0000000..d9686bb
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.robotests;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+
+public class SysuiRoboBase {
+ public Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index e1346ea..0000c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -118,13 +118,6 @@
}
@Test
- public void testCollapsePanels() {
- mCommandQueue.animateCollapsePanels();
- waitForIdleSync();
- verify(mCallbacks).animateCollapsePanels(eq(0), eq(false));
- }
-
- @Test
public void testExpandSettings() {
String panel = "some_panel";
mCommandQueue.animateExpandSettingsPanel(panel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index ed2afe7..915924f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -41,7 +41,6 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d5bfe1f..c17c5b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -136,7 +136,7 @@
StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
verify(mCentralSurfaces).updateQsExpansionEnabled();
- verify(mShadeController).animateCollapsePanels();
+ verify(mShadeController).animateCollapseShade();
// Trying to open it does nothing.
mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -154,7 +154,7 @@
mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
StatusBarManager.DISABLE2_NONE, false);
verify(mCentralSurfaces).updateQsExpansionEnabled();
- verify(mShadeController, never()).animateCollapsePanels();
+ verify(mShadeController, never()).animateCollapseShade();
// Can now be opened.
mSbcqCallbacks.animateExpandNotificationsPanel();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 013e727..ed84e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -392,10 +392,21 @@
return null;
}).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
- mShadeController = spy(new ShadeControllerImpl(mCommandQueue,
- mStatusBarStateController, mNotificationShadeWindowController,
- mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
- () -> Optional.of(mCentralSurfaces), () -> mAssistManager));
+ mShadeController = spy(new ShadeControllerImpl(
+ mCommandQueue,
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mStatusBarKeyguardViewManager,
+ mStatusBarWindowController,
+ mNotificationShadeWindowController,
+ mContext.getSystemService(WindowManager.class),
+ () -> mAssistManager,
+ () -> mNotificationGutsManager
+ ));
+ mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+ mShadeController.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
+ mShadeController.setNotificationPresenter(mNotificationPresenter);
when(mOperatorNameViewControllerFactory.create(any()))
.thenReturn(mOperatorNameViewController);
@@ -492,6 +503,7 @@
return mViewRootImpl;
}
};
+ mCentralSurfaces.initShadeVisibilityListener();
when(mViewRootImpl.getOnBackInvokedDispatcher())
.thenReturn(mOnBackInvokedDispatcher);
when(mKeyguardViewMediator.registerCentralSurfaces(
@@ -807,7 +819,7 @@
when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
mOnBackInvokedCallback.getValue().onBackInvoked();
- verify(mShadeController).animateCollapsePanels();
+ verify(mShadeController).animateCollapseShade();
}
@Test
@@ -1030,7 +1042,7 @@
}
@Test
- public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+ public void collapseShade_callsanimateCollapseShade_whenExpanded() {
// GIVEN the shade is expanded
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1038,12 +1050,12 @@
// WHEN collapseShade is called
mCentralSurfaces.collapseShade();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController).animateCollapseShade();
}
@Test
- public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+ public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
// GIVEN the shade is collapsed
mCentralSurfaces.onShadeExpansionFullyChanged(false);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1051,12 +1063,12 @@
// WHEN collapseShade is called
mCentralSurfaces.collapseShade();
- // VERIFY that animateCollapsePanels is NOT called
- verify(mShadeController, never()).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is NOT called
+ verify(mShadeController, never()).animateCollapseShade();
}
@Test
- public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+ public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() {
// GIVEN the shade is expanded & flag enabled
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1065,12 +1077,12 @@
// WHEN collapseShadeForBugreport is called
mCentralSurfaces.collapseShadeForBugreport();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController).animateCollapseShade();
}
@Test
- public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+ public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() {
// GIVEN the shade is expanded & flag enabled
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1079,8 +1091,8 @@
// WHEN collapseShadeForBugreport is called
mCentralSurfaces.collapseShadeForBugreport();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController, never()).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController, never()).animateCollapseShade();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index bf5186b..e467d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -307,6 +307,17 @@
}
@Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
// Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
// the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ce54d78..cae414a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -263,7 +263,7 @@
while (!runnables.isEmpty()) runnables.remove(0).run();
// Then
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
eq(false) /* animate */, any(), any());
@@ -296,7 +296,7 @@
verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
// This is called regardless, and simply short circuits when there is nothing to do.
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
@@ -329,7 +329,7 @@
// Then
verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
@@ -357,7 +357,7 @@
// Then
verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
diff --git a/services/api/current.txt b/services/api/current.txt
index 42ae10e..35a4dcb 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -99,6 +99,7 @@
method public int getAppId();
method @NonNull public String getPackageName();
method @Nullable public String getPrimaryCpuAbi();
+ method @Nullable public String getSeInfo();
method @Nullable public String getSecondaryCpuAbi();
method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
new file mode 100644
index 0000000..ec7e993
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import android.annotation.NonNull;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+
+/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
+public class SensorController {
+
+ private static final String TAG = "SensorController";
+
+ private final Object mLock;
+ private final int mVirtualDeviceId;
+ @GuardedBy("mLock")
+ private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+
+ private final SensorManagerInternal mSensorManagerInternal;
+
+ public SensorController(@NonNull Object lock, int virtualDeviceId) {
+ mLock = lock;
+ mVirtualDeviceId = virtualDeviceId;
+ mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
+ }
+
+ void close() {
+ synchronized (mLock) {
+ final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator =
+ mSensorDescriptors.entrySet().iterator();
+ if (iterator.hasNext()) {
+ final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next();
+ final IBinder token = entry.getKey();
+ final SensorDescriptor sensorDescriptor = entry.getValue();
+ iterator.remove();
+ closeSensorDescriptorLocked(token, sensorDescriptor);
+ }
+ }
+ }
+
+ void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
+ Objects.requireNonNull(deviceToken);
+ Objects.requireNonNull(config);
+ try {
+ createSensorInternal(deviceToken, config);
+ } catch (SensorCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual sensor '" + config.getName() + "'.", e);
+ }
+ }
+
+ private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
+ throws SensorCreationException {
+ final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
+ (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
+ IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
+ if (callback != null) {
+ try {
+ callback.onStateChanged(
+ enabled, samplingPeriodMicros, batchReportLatencyMicros);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to call sensor callback.", e);
+ }
+ }
+ };
+
+ final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
+ config.getType(), config.getName(),
+ config.getVendor() == null ? "" : config.getVendor(),
+ runtimeSensorCallback);
+ if (handle <= 0) {
+ throw new SensorCreationException("Received an invalid virtual sensor handle.");
+ }
+
+ // The handle is valid from here, so ensure that all failures clean it up.
+ final BinderDeathRecipient binderDeathRecipient;
+ try {
+ binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+ deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException e) {
+ mSensorManagerInternal.removeRuntimeSensor(handle);
+ throw new SensorCreationException("Client died before sensor could be created.", e);
+ }
+
+ synchronized (mLock) {
+ SensorDescriptor sensorDescriptor = new SensorDescriptor(
+ handle, config.getType(), config.getName(), binderDeathRecipient);
+ mSensorDescriptors.put(deviceToken, sensorDescriptor);
+ }
+ }
+
+ boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+ Objects.requireNonNull(token);
+ Objects.requireNonNull(event);
+ synchronized (mLock) {
+ final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
+ if (sensorDescriptor == null) {
+ throw new IllegalArgumentException("Could not send sensor event for given token");
+ }
+ return mSensorManagerInternal.sendSensorEvent(
+ sensorDescriptor.getHandle(), sensorDescriptor.getType(),
+ event.getTimestampNanos(), event.getValues());
+ }
+ }
+
+ void unregisterSensor(@NonNull IBinder token) {
+ Objects.requireNonNull(token);
+ synchronized (mLock) {
+ final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
+ if (sensorDescriptor == null) {
+ throw new IllegalArgumentException("Could not unregister sensor for given token");
+ }
+ closeSensorDescriptorLocked(token, sensorDescriptor);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) {
+ token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0);
+ final int handle = sensorDescriptor.getHandle();
+ mSensorManagerInternal.removeRuntimeSensor(handle);
+ }
+
+
+ void dump(@NonNull PrintWriter fout) {
+ fout.println(" SensorController: ");
+ synchronized (mLock) {
+ fout.println(" Active descriptors: ");
+ for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) {
+ fout.println(" handle: " + sensorDescriptor.getHandle());
+ fout.println(" type: " + sensorDescriptor.getType());
+ fout.println(" name: " + sensorDescriptor.getName());
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) {
+ synchronized (mLock) {
+ mSensorDescriptors.put(deviceToken,
+ new SensorDescriptor(handle, type, name, () -> {}));
+ }
+ }
+
+ @VisibleForTesting
+ Map<IBinder, SensorDescriptor> getSensorDescriptors() {
+ synchronized (mLock) {
+ return mSensorDescriptors;
+ }
+ }
+
+ @VisibleForTesting
+ static final class SensorDescriptor {
+
+ private final int mHandle;
+ private final IBinder.DeathRecipient mDeathRecipient;
+ private final int mType;
+ private final String mName;
+
+ SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) {
+ mHandle = handle;
+ mDeathRecipient = deathRecipient;
+ mType = type;
+ mName = name;
+ }
+ public int getHandle() {
+ return mHandle;
+ }
+ public int getType() {
+ return mType;
+ }
+ public String getName() {
+ return mName;
+ }
+ public IBinder.DeathRecipient getDeathRecipient() {
+ return mDeathRecipient;
+ }
+ }
+
+ private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+ private final IBinder mDeviceToken;
+
+ BinderDeathRecipient(IBinder deviceToken) {
+ mDeviceToken = deviceToken;
+ }
+
+ @Override
+ public void binderDied() {
+ // All callers are expected to call {@link VirtualDevice#unregisterSensor} before
+ // quitting, which removes this death recipient. If this is invoked, the remote end
+ // died, or they disposed of the object without properly unregistering.
+ Slog.e(TAG, "Virtual sensor controller binder died");
+ unregisterSensor(mDeviceToken);
+ }
+ }
+
+ /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */
+ private static class SensorCreationException extends Exception {
+ SensorCreationException(String message) {
+ super(message);
+ }
+ SensorCreationException(String message, Exception cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 7e82918..828f302 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -38,6 +38,8 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -76,6 +78,7 @@
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -98,6 +101,7 @@
private final int mOwnerUid;
private final int mDeviceId;
private final InputController mInputController;
+ private final SensorController mSensorController;
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -160,6 +164,7 @@
ownerUid,
deviceId,
/* inputController= */ null,
+ /* sensorController= */ null,
listener,
pendingTrampolineCallback,
activityListener,
@@ -175,6 +180,7 @@
int ownerUid,
int deviceId,
InputController inputController,
+ SensorController sensorController,
OnDeviceCloseListener listener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -198,6 +204,11 @@
} else {
mInputController = inputController;
}
+ if (sensorController == null) {
+ mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
+ } else {
+ mSensorController = sensorController;
+ }
mListener = listener;
try {
token.linkToDeath(this, 0);
@@ -319,11 +330,12 @@
mListener.onClose(mAssociationInfo.getId());
mAppToken.unlinkToDeath(this, 0);
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.close();
+ mSensorController.close();
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -403,12 +415,12 @@
+ "this virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -430,12 +442,12 @@
+ "this virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -457,11 +469,11 @@
+ "virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -491,12 +503,12 @@
+ screenSize);
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createTouchscreen(deviceName, vendorId, productId,
deviceToken, displayId, screenSize);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -506,92 +518,92 @@
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to unregister this input device");
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.unregisterInputDevice(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public int getInputDeviceId(IBinder token) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getInputDeviceId(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendDpadKeyEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendKeyEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendButtonEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendTouchEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendRelativeEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendScrollEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public PointF getCursorPosition(IBinder token) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getCursorPosition(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -601,7 +613,7 @@
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to unregister this input device");
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
@@ -610,7 +622,50 @@
}
}
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void createVirtualSensor(
+ @NonNull IBinder deviceToken,
+ @NonNull VirtualSensorConfig config) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to create a virtual sensor");
+ Objects.requireNonNull(config);
+ Objects.requireNonNull(deviceToken);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mSensorController.createSensor(deviceToken, config);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void unregisterSensor(@NonNull IBinder token) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to unregister a virtual sensor");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mSensorController.unregisterSensor(token);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to send a virtual sensor event");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mSensorController.sendSensorEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -627,6 +682,7 @@
fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
+ mSensorController.dump(fout);
}
GenericWindowPolicyController createWindowPolicyController(
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 fe26700..2b62f69 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -32,6 +32,7 @@
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
+import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
@@ -280,7 +281,22 @@
@Override
public void onClose(int associationId) {
synchronized (mVirtualDeviceManagerLock) {
- mVirtualDevices.remove(associationId);
+ VirtualDeviceImpl removedDevice =
+ mVirtualDevices.removeReturnOld(associationId);
+ if (removedDevice != null) {
+ Intent i = new Intent(
+ VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+ i.putExtra(
+ VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
+ removedDevice.getDeviceId());
+ i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
mAppsOnVirtualDevices.remove(associationId);
if (cameraAccessController != null) {
cameraAccessController.stopObservingIfNeeded();
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 04ba757..1eebd01 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,6 +153,11 @@
"bcast_extra_running_urgent_process_queues";
private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
+ public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
+ private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
+ "bcast_max_consecutive_urgent_dispatches";
+ private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
+
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
* to dispatch to a "running" process queue before we retire them back to
@@ -333,6 +338,9 @@
EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES,
DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
+ MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
+ KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+ DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0f9c775..66d7fc9 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -147,6 +147,12 @@
private boolean mActiveViaColdStart;
/**
+ * Number of consecutive urgent broadcasts that have been dispatched
+ * since the last non-urgent dispatch.
+ */
+ private int mActiveCountConsecutiveUrgent;
+
+ /**
* Count of pending broadcasts of these various flavors.
*/
private int mCountForeground;
@@ -546,19 +552,47 @@
* {@link #isEmpty()} being false.
*/
SomeArgs removeNextBroadcast() {
- ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+ final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+ if (queue == mPendingUrgent) {
+ mActiveCountConsecutiveUrgent++;
+ } else {
+ mActiveCountConsecutiveUrgent = 0;
+ }
return queue.removeFirst();
}
@Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
- if (!mPendingUrgent.isEmpty()) {
- return mPendingUrgent;
- } else if (!mPending.isEmpty()) {
- return mPending;
+ ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
+ ArrayDeque<SomeArgs> nextNormal = null;
+ if (!mPending.isEmpty()) {
+ nextNormal = mPending;
} else if (!mPendingOffload.isEmpty()) {
- return mPendingOffload;
+ nextNormal = mPendingOffload;
}
- return null;
+ // nothing urgent pending, no further decisionmaking
+ if (nextUrgent == null) {
+ return nextNormal;
+ }
+ // nothing but urgent pending, also no further decisionmaking
+ if (nextNormal == null) {
+ return nextUrgent;
+ }
+
+ // Starvation mitigation: although we prioritize urgent broadcasts by default,
+ // we allow non-urgent deliveries to make steady progress even if urgent
+ // broadcasts are arriving faster than they can be dispatched.
+ //
+ // We do not try to defer to the next non-urgent broadcast if that broadcast
+ // is ordered and still blocked on delivery to other recipients.
+ final SomeArgs nextNormalArgs = nextNormal.peekFirst();
+ final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
+ final int nextNormalIndex = nextNormalArgs.argi1;
+ final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
+ final boolean canTakeNormal =
+ mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
+ && rNormal.enqueueTime <= rUrgent.enqueueTime
+ && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
+ return canTakeNormal ? nextNormal : nextUrgent;
}
/**
@@ -710,6 +744,18 @@
}
}
+ private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
+ final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
+
+ // We might be blocked waiting for other receivers to finish,
+ // typically for an ordered broadcast or priority traunches
+ if (r.terminalCount < blockedUntilTerminalCount
+ && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Update {@link #getRunnableAt()} if it's currently invalidated.
*/
@@ -718,13 +764,11 @@
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
- final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
final long runnableAt = r.enqueueTime;
- // We might be blocked waiting for other receivers to finish,
- // typically for an ordered broadcast or priority traunches
- if (r.terminalCount < blockedUntilTerminalCount
- && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+ // If we're specifically queued behind other ordered dispatch activity,
+ // we aren't runnable yet
+ if (blockedOnOrderedDispatch(r, index)) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_BLOCKED;
return;
diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING
index feb2b4f..82840ee 100644
--- a/services/core/java/com/android/server/app/TEST_MAPPING
+++ b/services/core/java/com/android/server/app/TEST_MAPPING
@@ -9,6 +9,23 @@
]
},
{
+ "name": "CtsStatsdAtomHostTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.cts.statsdatom.gamemanager"
+ }
+ ],
+ "file_patterns": [
+ "(/|^)GameManagerService.java"
+ ]
+ },
+ {
"name": "FrameworksMockingServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
similarity index 97%
rename from services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
rename to services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index f6fff35..8d1da71 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -20,7 +20,7 @@
import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager.opRestrictsRead;
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
import android.Manifest;
import android.annotation.NonNull;
@@ -56,7 +56,7 @@
* Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
* In the future this class will also include mode callbacks and op restrictions.
*/
-public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
+public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface {
static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
@@ -84,9 +84,9 @@
private static final int UID_ANY = -2;
- LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
- @NonNull Object lock, Handler handler, Context context,
- SparseArray<int[]> switchedOps) {
+ AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
+ @NonNull Object lock, Handler handler, Context context,
+ SparseArray<int[]> switchedOps) {
this.mPersistenceScheduler = persistenceScheduler;
this.mLock = lock;
this.mHandler = handler;
@@ -456,7 +456,7 @@
final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
if (reportedPackageNames == null) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+ AppOpsCheckingServiceImpl::notifyOpChanged,
this, callback, code, uid, (String) null));
} else {
@@ -464,7 +464,7 @@
for (int j = 0; j < reportedPackageCount; j++) {
final String reportedPackageName = reportedPackageNames.valueAt(j);
mHandler.sendMessage(PooledLambda.obtainMessage(
- LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+ AppOpsCheckingServiceImpl::notifyOpChanged,
this, callback, code, uid, reportedPackageName));
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
new file mode 100644
index 0000000..d8d0d48
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager.Mode;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
+ * This interface also includes functions for added and removing op mode watchers.
+ * In the future this interface will also include op restrictions.
+ */
+public interface AppOpsCheckingServiceInterface {
+ /**
+ * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
+ * Returns an empty SparseIntArray if nothing is set.
+ * @param uid for which we need the app-ops and their modes.
+ */
+ SparseIntArray getNonDefaultUidModes(int uid);
+
+ /**
+ * Returns the app-op mode for a particular app-op of a uid.
+ * Returns default op mode if the op mode for particular uid and op is not set.
+ * @param uid user id for which we need the mode.
+ * @param op app-op for which we need the mode.
+ * @return mode of the app-op.
+ */
+ int getUidMode(int uid, int op);
+
+ /**
+ * Set the app-op mode for a particular uid and op.
+ * The mode is not set if the mode is the same as the default mode for the op.
+ * @param uid user id for which we want to set the mode.
+ * @param op app-op for which we want to set the mode.
+ * @param mode mode for the app-op.
+ * @return true if op mode is changed.
+ */
+ boolean setUidMode(int uid, int op, @Mode int mode);
+
+ /**
+ * Gets the app-op mode for a particular package.
+ * Returns default op mode if the op mode for the particular package is not set.
+ * @param packageName package name for which we need the op mode.
+ * @param op app-op for which we need the mode.
+ * @param userId user id associated with the package.
+ * @return the mode of the app-op.
+ */
+ int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
+
+ /**
+ * Sets the app-op mode for a particular package.
+ * @param packageName package name for which we need to set the op mode.
+ * @param op app-op for which we need to set the mode.
+ * @param mode the mode of the app-op.
+ * @param userId user id associated with the package.
+ *
+ */
+ void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+
+ /**
+ * Stop tracking any app-op modes for a package.
+ * @param packageName Name of the package for which we want to remove all mode tracking.
+ * @param userId user id associated with the package.
+ */
+ boolean removePackage(@NonNull String packageName, @UserIdInt int userId);
+
+ /**
+ * Stop tracking any app-op modes for this uid.
+ * @param uid user id for which we want to remove all tracking.
+ */
+ void removeUid(int uid);
+
+ /**
+ * Returns true if all uid modes for this uid are
+ * in default state.
+ * @param uid user id
+ */
+ boolean areUidModesDefault(int uid);
+
+ /**
+ * Returns true if all package modes for this package name are
+ * in default state.
+ * @param packageName package name.
+ * @param userId user id associated with the package.
+ */
+ boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+
+ /**
+ * Stop tracking app-op modes for all uid and packages.
+ */
+ void clearAllModes();
+
+ /**
+ * Registers changedListener to listen to op's mode change.
+ * @param changedListener the listener that must be trigger on the op's mode change.
+ * @param op op representing the app-op whose mode change needs to be listened to.
+ */
+ void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+
+ /**
+ * Registers changedListener to listen to package's app-op's mode change.
+ * @param changedListener the listener that must be trigger on the mode change.
+ * @param packageName of the package whose app-op's mode change needs to be listened to.
+ */
+ void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+ @NonNull String packageName);
+
+ /**
+ * Stop the changedListener from triggering on any mode change.
+ * @param changedListener the listener that needs to be removed.
+ */
+ void removeListener(@NonNull OnOpModeChangedListener changedListener);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
+ * @param op app-op whose mode change is being listened to.
+ */
+ ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
+ * @param packageName of package whose app-op's mode change is being listened to.
+ */
+ ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed by triggering the change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+ */
+ void notifyWatchersOfChange(int op, int uid);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed by triggering the change listener.
+ * @param changedListener the change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op
+ * @param packageName package name that is associated with the app-op
+ */
+ void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+ @Nullable String packageName);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed to all packages associated with the uid by
+ * triggering the appropriate change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op
+ * @param onlyForeground true if only watchers that
+ * @param callbackToIgnore callback that should be ignored.
+ */
+ void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+ @Nullable OnOpModeChangedListener callbackToIgnore);
+
+ /**
+ * TODO: Move hasForegroundWatchers and foregroundOps into this.
+ * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
+ * foregroundOps.
+ * @param uid for which the app-op's mode needs to be marked.
+ * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+ * @return foregroundOps.
+ */
+ SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+
+ /**
+ * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
+ * foregroundOps.
+ * @param packageName for which the app-op's mode needs to be marked.
+ * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+ * @param userId user id associated with the package.
+ * @return foregroundOps.
+ */
+ SparseBooleanArray evalForegroundPackageOps(String packageName,
+ SparseBooleanArray foregroundOps, @UserIdInt int userId);
+
+ /**
+ * Dump op mode and package mode listeners and their details.
+ * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
+ * app-op, only the watchers for that app-op are dumped.
+ * @param dumpUid uid for which we want to dump op mode watchers.
+ * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
+ * @param printWriter writer to dump to.
+ */
+ boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index adfd2af..af5b07e 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,7 @@
private Context mContext;
private Handler mHandler;
- private AppOpsServiceInterface mAppOpsServiceInterface;
+ private AppOpsCheckingServiceInterface mAppOpsServiceInterface;
// Map from (Object token) to (int code) to (boolean restricted)
private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,7 +56,7 @@
mUserRestrictionExcludedPackageTags = new ArrayMap<>();
public AppOpsRestrictionsImpl(Context context, Handler handler,
- AppOpsServiceInterface appOpsServiceInterface) {
+ AppOpsCheckingServiceInterface appOpsServiceInterface) {
mContext = context;
mHandler = handler;
mAppOpsServiceInterface = appOpsServiceInterface;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7e00c32..39338c6 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,55 +18,22 @@
import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
-import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
-import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
-import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
-import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
-import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
-import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
-import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
-import static android.app.AppOpsManager.OpEventProxyInfo;
-import static android.app.AppOpsManager.RestrictionBypass;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
-import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
import static android.app.AppOpsManager._NUM_OP;
-import static android.app.AppOpsManager.extractFlagsFromKey;
-import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.modeToName;
-import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -75,21 +42,16 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.AppOpsManager.AttributedOpEntry;
import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.Mode;
-import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.AsyncNotedAppOp;
import android.app.RuntimeAppOpAccessMessage;
import android.app.SyncNotedAppOp;
-import android.app.admin.DevicePolicyManagerInternal;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -97,15 +59,11 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
-import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.PackageTagsList;
import android.os.Process;
@@ -116,22 +74,14 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.storage.StorageManagerInternal;
-import android.permission.PermissionManager;
-import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.KeyValueListParser;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
-import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
@@ -143,59 +93,37 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsStartedCallback;
import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.os.Clock;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
-import com.android.server.LockGuard;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
import com.android.server.pm.PackageList;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedAttribution;
import com.android.server.policy.AppOpsPolicy;
-import dalvik.annotation.optimization.NeverCompile;
-
-import libcore.util.EmptyArray;
-
import org.json.JSONException;
import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
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.Map;
import java.util.Objects;
import java.util.Scanner;
-import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
-public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
+/**
+ * The system service component to {@link AppOpsManager}.
+ */
+public class AppOpsService extends IAppOpsService.Stub {
+
+ private final AppOpsServiceInterface mAppOpsService;
+
static final String TAG = "AppOps";
static final boolean DEBUG = false;
@@ -204,57 +132,19 @@
*/
private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
- private static final int NO_VERSION = -1;
- /** Increment by one every time and add the corresponding upgrade logic in
- * {@link #upgradeLocked(int)} below. The first version was 1 */
- private static final int CURRENT_VERSION = 1;
-
- // Write at most every 30 minutes.
- static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
-
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
- private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
- OP_PLAY_AUDIO,
- OP_RECORD_AUDIO,
- OP_CAMERA,
- OP_VIBRATE,
- };
-
private static final int MAX_UNFORWARDED_OPS = 10;
- private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
final Context mContext;
- final AtomicFile mFile;
private final @Nullable File mNoteOpCallerStacktracesFile;
final Handler mHandler;
- /**
- * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
- * objects
- */
- @GuardedBy("this")
- final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
- new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
-
- /**
- * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
- * new objects
- */
- @GuardedBy("this")
- final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
- new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
- MAX_UNUSED_POOLED_OBJECTS);
-
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
= new AppOpsManagerInternalImpl();
- @Nullable private final DevicePolicyManagerInternal dpmi =
- LocalServices.getService(DevicePolicyManagerInternal.class);
-
- private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/**
* Registered callbacks, called from {@link #collectAsyncNotedOp}.
@@ -281,54 +171,9 @@
boolean mWriteNoteOpsScheduled;
- boolean mWriteScheduled;
- boolean mFastWriteScheduled;
- final Runnable mWriteRunner = new Runnable() {
- public void run() {
- synchronized (AppOpsService.this) {
- mWriteScheduled = false;
- mFastWriteScheduled = false;
- AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
- @Override protected Void doInBackground(Void... params) {
- writeState();
- return null;
- }
- };
- task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
- }
- }
- };
-
- @GuardedBy("this")
- @VisibleForTesting
- final SparseArray<UidState> mUidStates = new SparseArray<>();
-
- volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
-
- /*
- * These are app op restrictions imposed per user from various parties.
- */
- private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
- new ArrayMap<>();
-
- /*
- * These are app op restrictions imposed globally from various parties within the system.
- */
- private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
- new ArrayMap<>();
-
- SparseIntArray mProfileOwners;
-
private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
- /**
- * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
- * changed
- */
- private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
-
- private ActivityManagerInternal mActivityManagerInternal;
/** Package sampled for message collection in the current session */
@GuardedBy("this")
@@ -362,545 +207,8 @@
/** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
private @Nullable PackageManagerInternal mPackageManagerInternal;
- /** Interface for app-op modes.*/
- @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
-
- /** Interface for app-op restrictions.*/
- @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
-
- private AppOpsUidStateTracker mUidStateTracker;
-
- /** Hands the definition of foreground and uid states */
- @GuardedBy("this")
- public AppOpsUidStateTracker getUidStateTracker() {
- if (mUidStateTracker == null) {
- mUidStateTracker = new AppOpsUidStateTrackerImpl(
- LocalServices.getService(ActivityManagerInternal.class),
- mHandler,
- r -> {
- synchronized (AppOpsService.this) {
- r.run();
- }
- },
- Clock.SYSTEM_CLOCK, mConstants);
-
- mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
- this::onUidStateChanged);
- }
- return mUidStateTracker;
- }
-
- /**
- * All times are in milliseconds. These constants are kept synchronized with the system
- * global Settings. Any access to this class or its fields should be done while
- * holding the AppOpsService lock.
- */
- final class Constants extends ContentObserver {
-
- /**
- * How long we want for a drop in uid state from top to settle before applying it.
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
- */
- public long TOP_STATE_SETTLE_TIME;
-
- /**
- * How long we want for a drop in uid state from foreground to settle before applying it.
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
- */
- public long FG_SERVICE_STATE_SETTLE_TIME;
-
- /**
- * How long we want for a drop in uid state from background to settle before applying it.
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
- */
- public long BG_STATE_SETTLE_TIME;
-
- private final KeyValueListParser mParser = new KeyValueListParser(',');
- private ContentResolver mResolver;
-
- public Constants(Handler handler) {
- super(handler);
- updateConstants();
- }
-
- public void startMonitoring(ContentResolver resolver) {
- mResolver = resolver;
- mResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
- false, this);
- updateConstants();
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- updateConstants();
- }
-
- private void updateConstants() {
- String value = mResolver != null ? Settings.Global.getString(mResolver,
- Settings.Global.APP_OPS_CONSTANTS) : "";
-
- synchronized (AppOpsService.this) {
- try {
- mParser.setString(value);
- } catch (IllegalArgumentException e) {
- // Failed to parse the settings string, log this and move on
- // with defaults.
- Slog.e(TAG, "Bad app ops settings", e);
- }
- TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
- FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
- BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
- }
- }
-
- void dump(PrintWriter pw) {
- pw.println(" Settings:");
-
- pw.print(" "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
- TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
- pw.println();
- pw.print(" "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
- TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
- pw.println();
- pw.print(" "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
- TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
- pw.println();
- }
- }
-
- @VisibleForTesting
- final Constants mConstants;
-
- @VisibleForTesting
- final class UidState {
- public final int uid;
-
- public ArrayMap<String, Ops> pkgOps;
-
- // true indicates there is an interested observer, false there isn't but it has such an op
- //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
- public SparseBooleanArray foregroundOps;
- public boolean hasForegroundWatchers;
-
- public UidState(int uid) {
- this.uid = uid;
- }
-
- public void clear() {
- mAppOpsServiceInterface.removeUid(uid);
- if (pkgOps != null) {
- for (String packageName : pkgOps.keySet()) {
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- }
- }
- pkgOps = null;
- }
-
- public boolean isDefault() {
- boolean areAllPackageModesDefault = true;
- if (pkgOps != null) {
- for (String packageName : pkgOps.keySet()) {
- if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
- UserHandle.getUserId(uid))) {
- areAllPackageModesDefault = false;
- break;
- }
- }
- }
- return (pkgOps == null || pkgOps.isEmpty())
- && mAppOpsServiceInterface.areUidModesDefault(uid)
- && areAllPackageModesDefault;
- }
-
- // Functions for uid mode access and manipulation.
- public SparseIntArray getNonDefaultUidModes() {
- return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
- }
-
- public int getUidMode(int op) {
- return mAppOpsServiceInterface.getUidMode(uid, op);
- }
-
- public boolean setUidMode(int op, int mode) {
- return mAppOpsServiceInterface.setUidMode(uid, op, mode);
- }
-
- @SuppressWarnings("GuardedBy")
- int evalMode(int op, int mode) {
- return getUidStateTracker().evalMode(uid, op, mode);
- }
-
- public void evalForegroundOps() {
- foregroundOps = null;
- foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
- if (pkgOps != null) {
- for (int i = pkgOps.size() - 1; i >= 0; i--) {
- foregroundOps = mAppOpsServiceInterface
- .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
- UserHandle.getUserId(uid));
- }
- }
- hasForegroundWatchers = false;
- if (foregroundOps != null) {
- for (int i = 0; i < foregroundOps.size(); i++) {
- if (foregroundOps.valueAt(i)) {
- hasForegroundWatchers = true;
- break;
- }
- }
- }
- }
-
- @SuppressWarnings("GuardedBy")
- public int getState() {
- return getUidStateTracker().getUidState(uid);
- }
-
- @SuppressWarnings("GuardedBy")
- public void dump(PrintWriter pw, long nowElapsed) {
- getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
- }
- }
-
- final static class Ops extends SparseArray<Op> {
- final String packageName;
- final UidState uidState;
-
- /**
- * The restriction properties of the package. If {@code null} it could not have been read
- * yet and has to be refreshed.
- */
- @Nullable RestrictionBypass bypass;
-
- /** Lazily populated cache of attributionTags of this package */
- final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
-
- /**
- * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
- * than or equal to {@link #knownAttributionTags}.
- */
- final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
-
- Ops(String _packageName, UidState _uidState) {
- packageName = _packageName;
- uidState = _uidState;
- }
- }
-
- /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
- private static final class PackageVerificationResult {
-
- final RestrictionBypass bypass;
- final boolean isAttributionTagValid;
-
- PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
- this.bypass = bypass;
- this.isAttributionTagValid = isAttributionTagValid;
- }
- }
-
- final class Op {
- int op;
- int uid;
- final UidState uidState;
- final @NonNull String packageName;
-
- /** attributionTag -> AttributedOp */
- final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
-
- Op(UidState uidState, String packageName, int op, int uid) {
- this.op = op;
- this.uid = uid;
- this.uidState = uidState;
- this.packageName = packageName;
- }
-
- @Mode int getMode() {
- return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
- UserHandle.getUserId(this.uid));
- }
- void setMode(@Mode int mode) {
- mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
- UserHandle.getUserId(this.uid));
- }
-
- void removeAttributionsWithNoTime() {
- for (int i = mAttributions.size() - 1; i >= 0; i--) {
- if (!mAttributions.valueAt(i).hasAnyTime()) {
- mAttributions.removeAt(i);
- }
- }
- }
-
- private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
- @Nullable String attributionTag) {
- AttributedOp attributedOp;
-
- attributedOp = mAttributions.get(attributionTag);
- if (attributedOp == null) {
- attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
- mAttributions.put(attributionTag, attributedOp);
- }
-
- return attributedOp;
- }
-
- @NonNull OpEntry createEntryLocked() {
- final int numAttributions = mAttributions.size();
-
- final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
- new ArrayMap<>(numAttributions);
- for (int i = 0; i < numAttributions; i++) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- }
-
- return new OpEntry(op, getMode(), attributionEntries);
- }
-
- @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
- final int numAttributions = mAttributions.size();
-
- final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
- for (int i = 0; i < numAttributions; i++) {
- if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- break;
- }
- }
-
- return new OpEntry(op, getMode(), attributionEntries);
- }
-
- boolean isRunning() {
- final int numAttributions = mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if (mAttributions.valueAt(i).isRunning()) {
- return true;
- }
- }
-
- return false;
- }
- }
-
- final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
- final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
- /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
- public static final int ALL_OPS = -2;
-
- // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
- // Otherwise we can just use the IBinder object.
- private final IAppOpsCallback mCallback;
-
- ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
- int callingUid, int callingPid) {
- super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
- this.mCallback = callback;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("ModeCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, getWatchingUid());
- sb.append(" flags=0x");
- sb.append(Integer.toHexString(getFlags()));
- switch (getWatchedOpCode()) {
- case OP_NONE:
- break;
- case ALL_OPS:
- sb.append(" op=(all)");
- break;
- default:
- sb.append(" op=");
- sb.append(opToName(getWatchedOpCode()));
- break;
- }
- sb.append(" from uid=");
- UserHandle.formatUid(sb, getCallingUid());
- sb.append(" pid=");
- sb.append(getCallingPid());
- sb.append('}');
- return sb.toString();
- }
-
- void unlinkToDeath() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingMode(mCallback);
- }
-
- @Override
- public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
- mCallback.opChanged(op, uid, packageName);
- }
- }
-
- final class ActiveCallback implements DeathRecipient {
- final IAppOpsActiveCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("ActiveCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingActive(mCallback);
- }
- }
-
- final class StartedCallback implements DeathRecipient {
- final IAppOpsStartedCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("StartedCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingStarted(mCallback);
- }
- }
-
- final class NotedCallback implements DeathRecipient {
- final IAppOpsNotedCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("NotedCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingNoted(mCallback);
- }
- }
-
- /**
- * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
- */
- static void onClientDeath(@NonNull AttributedOp attributedOp,
- @NonNull IBinder clientId) {
- attributedOp.onClientDeath(clientId);
- }
-
-
/**
* Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
* so that we do not log the same operation twice between instances
@@ -925,20 +233,12 @@
}
public AppOpsService(File storagePath, Handler handler, Context context) {
- mContext = context;
+ this(handler, context, new AppOpsServiceImpl(storagePath, handler, context));
+ }
- for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
- int switchCode = AppOpsManager.opToSwitch(switchedCode);
- mSwitchedOps.put(switchCode,
- ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
- }
- mAppOpsServiceInterface =
- new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
- mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
- mAppOpsServiceInterface);
-
- LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
- mFile = new AtomicFile(storagePath, "appops");
+ @VisibleForTesting
+ public AppOpsService(Handler handler, Context context,
+ AppOpsServiceInterface appOpsServiceInterface) {
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
"noteOpStackTraces.json");
@@ -946,185 +246,25 @@
} else {
mNoteOpCallerStacktracesFile = null;
}
+
+ mAppOpsService = appOpsServiceInterface;
+ mContext = context;
mHandler = handler;
- mConstants = new Constants(mHandler);
- readState();
}
+ /**
+ * Publishes binder and local service.
+ */
public void publish() {
ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
}
- /** Handler for work when packages are removed or updated */
- private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- String pkgName = intent.getData().getEncodedSchemeSpecificPart();
- int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
-
- if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
- synchronized (AppOpsService.this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
- mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
- Ops removedOps = uidState.pkgOps.remove(pkgName);
- if (removedOps != null) {
- scheduleFastWriteLocked();
- }
- }
- } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
- AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
- if (pkg == null) {
- return;
- }
-
- ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
- ArraySet<String> attributionTags = new ArraySet<>();
- attributionTags.add(null);
- if (pkg.getAttributions() != null) {
- int numAttributions = pkg.getAttributions().size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
- attributionTags.add(attribution.getTag());
-
- int numInheritFrom = attribution.getInheritFrom().size();
- for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
- inheritFromNum++) {
- dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
- attribution.getTag());
- }
- }
- }
-
- synchronized (AppOpsService.this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
-
- Ops ops = uidState.pkgOps.get(pkgName);
- if (ops == null) {
- return;
- }
-
- // Reset cached package properties to re-initialize when needed
- ops.bypass = null;
- ops.knownAttributionTags.clear();
-
- // Merge data collected for removed attributions into their successor
- // attributions
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = numAttributions - 1; attributionNum >= 0;
- attributionNum--) {
- String attributionTag = op.mAttributions.keyAt(attributionNum);
-
- if (attributionTags.contains(attributionTag)) {
- // attribution still exist after upgrade
- continue;
- }
-
- String newAttributionTag = dstAttributionTags.get(attributionTag);
-
- AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
- newAttributionTag);
- newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
- op.mAttributions.removeAt(attributionNum);
-
- scheduleFastWriteLocked();
- }
- }
- }
- }
- }
- };
-
+ /**
+ * Finishes boot sequence.
+ */
public void systemReady() {
- mConstants.startMonitoring(mContext.getContentResolver());
- mHistoricalRegistry.systemReady(mContext.getContentResolver());
-
- IntentFilter packageUpdateFilter = new IntentFilter();
- packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- packageUpdateFilter.addDataScheme("package");
-
- mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
- packageUpdateFilter, null, null);
-
- synchronized (this) {
- for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
- int uid = mUidStates.keyAt(uidNum);
- UidState uidState = mUidStates.valueAt(uidNum);
-
- String[] pkgsInUid = getPackagesForUid(uidState.uid);
- if (ArrayUtils.isEmpty(pkgsInUid)) {
- uidState.clear();
- mUidStates.removeAt(uidNum);
- scheduleFastWriteLocked();
- continue;
- }
-
- ArrayMap<String, Ops> pkgs = uidState.pkgOps;
- if (pkgs == null) {
- continue;
- }
-
- int numPkgs = pkgs.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- String pkg = pkgs.keyAt(pkgNum);
-
- String action;
- if (!ArrayUtils.contains(pkgsInUid, pkg)) {
- action = Intent.ACTION_PACKAGE_REMOVED;
- } else {
- action = Intent.ACTION_PACKAGE_REPLACED;
- }
-
- SystemServerInitThreadPool.submit(
- () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
- .setData(Uri.fromParts("package", pkg, null))
- .putExtra(Intent.EXTRA_UID, uid)),
- "Update app-ops uidState in case package " + pkg + " changed");
- }
- }
- }
-
- final IntentFilter packageSuspendFilter = new IntentFilter();
- packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
- packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
- mContext.registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
- final String[] changedPkgs = intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_PACKAGE_LIST);
- for (int code : OPS_RESTRICTED_ON_SUSPEND) {
- ArraySet<OnOpModeChangedListener> onModeChangedListeners;
- synchronized (AppOpsService.this) {
- onModeChangedListeners =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (onModeChangedListeners == null) {
- continue;
- }
- }
- for (int i = 0; i < changedUids.length; i++) {
- final int changedUid = changedUids[i];
- final String changedPkg = changedPkgs[i];
- // We trust packagemanager to insert matching uid and packageNames in the
- // extras
- notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
- }
- }
- }
- }, UserHandle.ALL, packageSuspendFilter, null, null);
+ mAppOpsService.systemReady();
final IntentFilter packageAddedFilter = new IntentFilter();
packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -1132,9 +272,8 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final Uri data = intent.getData();
- final String packageName = data.getSchemeSpecificPart();
+ final String packageName = intent.getData().getSchemeSpecificPart();
PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
if (isSamplingTarget(pi)) {
@@ -1169,8 +308,6 @@
}
}
});
-
- mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
/**
@@ -1185,132 +322,18 @@
mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
}
+ /**
+ * Notify when a package is removed
+ */
public void packageRemoved(int uid, String packageName) {
- synchronized (this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null) {
- return;
- }
-
- Ops removedOps = null;
-
- // Remove any package state if such.
- if (uidState.pkgOps != null) {
- removedOps = uidState.pkgOps.remove(packageName);
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- }
-
- // If we just nuked the last package state check if the UID is valid.
- if (removedOps != null && uidState.pkgOps.isEmpty()
- && getPackagesForUid(uid).length <= 0) {
- uidState.clear();
- mUidStates.remove(uid);
- }
-
- if (removedOps != null) {
- scheduleFastWriteLocked();
-
- final int numOps = removedOps.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- final Op op = removedOps.valueAt(opNum);
-
- final int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
- while (attributedOp.isRunning()) {
- attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
- }
- while (attributedOp.isPaused()) {
- attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
- }
- }
- }
- }
- }
-
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
- mHistoricalRegistry, uid, packageName));
+ mAppOpsService.packageRemoved(uid, packageName);
}
+ /**
+ * Notify when a uid is removed.
+ */
public void uidRemoved(int uid) {
- synchronized (this) {
- if (mUidStates.indexOfKey(uid) >= 0) {
- mUidStates.get(uid).clear();
- mUidStates.remove(uid);
- scheduleFastWriteLocked();
- }
- }
- }
-
- // The callback method from ForegroundPolicyInterface
- private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, true);
-
- if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
- for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
- if (!uidState.foregroundOps.valueAt(fgi)) {
- continue;
- }
- final int code = uidState.foregroundOps.keyAt(fgi);
-
- if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
- && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChangedForAllPkgsInUid,
- this, code, uidState.uid, true, null));
- } else if (uidState.pkgOps != null) {
- final ArraySet<OnOpModeChangedListener> listenerSet =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (listenerSet != null) {
- for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
- final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
- if ((listener.getFlags()
- & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
- || !listener.isWatchingUid(uidState.uid)) {
- continue;
- }
- for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
- final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
- if (op == null) {
- continue;
- }
- if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, listenerSet.valueAt(cbi), code, uidState.uid,
- uidState.pkgOps.keyAt(pkgi)));
- }
- }
- }
- }
- }
- }
- }
-
- if (uidState != null && uidState.pkgOps != null) {
- int numPkgs = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
-
- attributedOp.onUidStateChanged(state);
- }
- }
- }
- }
- }
+ mAppOpsService.uidRemoved(uid);
}
/**
@@ -1318,542 +341,60 @@
*/
public void updateUidProcState(int uid, int procState,
@ActivityManager.ProcessCapability int capability) {
- synchronized (this) {
- getUidStateTracker().updateUidProcState(uid, procState, capability);
- if (!mUidStates.contains(uid)) {
- UidState uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- onUidStateChanged(uid,
- AppOpsUidStateTracker.processStateToUidState(procState), false);
- }
- }
+ mAppOpsService.updateUidProcState(uid, procState, capability);
}
+ /**
+ * Initiates shutdown.
+ */
public void shutdown() {
- Slog.w(TAG, "Writing app ops before shutdown...");
- boolean doWrite = false;
- synchronized (this) {
- if (mWriteScheduled) {
- mWriteScheduled = false;
- mFastWriteScheduled = false;
- mHandler.removeCallbacks(mWriteRunner);
- doWrite = true;
- }
- }
- if (doWrite) {
- writeState();
- }
+ mAppOpsService.shutdown();
+
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
writeNoteOps();
}
-
- mHistoricalRegistry.shutdown();
- }
-
- private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
- ArrayList<AppOpsManager.OpEntry> resOps = null;
- if (ops == null) {
- resOps = new ArrayList<>();
- for (int j=0; j<pkgOps.size(); j++) {
- Op curOp = pkgOps.valueAt(j);
- resOps.add(getOpEntryForResult(curOp));
- }
- } else {
- for (int j=0; j<ops.length; j++) {
- Op curOp = pkgOps.get(ops[j]);
- if (curOp != null) {
- if (resOps == null) {
- resOps = new ArrayList<>();
- }
- resOps.add(getOpEntryForResult(curOp));
- }
- }
- }
- return resOps;
- }
-
- @Nullable
- private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
- @Nullable int[] ops) {
- final SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes == null) {
- return null;
- }
-
- int opModeCount = opModes.size();
- if (opModeCount == 0) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = null;
- if (ops == null) {
- resOps = new ArrayList<>();
- for (int i = 0; i < opModeCount; i++) {
- int code = opModes.keyAt(i);
- resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
- }
- } else {
- for (int j=0; j<ops.length; j++) {
- int code = ops[j];
- if (opModes.indexOfKey(code) >= 0) {
- if (resOps == null) {
- resOps = new ArrayList<>();
- }
- resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
- }
- }
- }
- return resOps;
- }
-
- private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
- return op.createEntryLocked();
}
@Override
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
- final int callingUid = Binder.getCallingUid();
- final boolean hasAllPackageAccess = mContext.checkPermission(
- Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
- Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
- ArrayList<AppOpsManager.PackageOps> res = null;
- synchronized (this) {
- final int uidStateCount = mUidStates.size();
- for (int i = 0; i < uidStateCount; i++) {
- UidState uidState = mUidStates.valueAt(i);
- if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
- continue;
- }
- ArrayMap<String, Ops> packages = uidState.pkgOps;
- final int packageCount = packages.size();
- for (int j = 0; j < packageCount; j++) {
- Ops pkgOps = packages.valueAt(j);
- ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
- if (resOps != null) {
- if (res == null) {
- res = new ArrayList<>();
- }
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- pkgOps.packageName, pkgOps.uidState.uid, resOps);
- // Caller can always see their packages and with a permission all.
- if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
- res.add(resPackage);
- }
- }
- }
- }
- }
- return res;
+ return mAppOpsService.getPackagesForOps(ops);
}
@Override
public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
int[] ops) {
- enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return Collections.emptyList();
- }
- synchronized (this) {
- Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
- /* edit */ false);
- if (pkgOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
- if (resOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- pkgOps.packageName, pkgOps.uidState.uid, resOps);
- res.add(resPackage);
- return res;
- }
- }
-
- private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
- final int callingUid = Binder.getCallingUid();
- // We get to access everything
- if (callingUid == Process.myPid()) {
- return;
- }
- // Apps can access their own data
- if (uid == callingUid && packageName != null
- && checkPackage(uid, packageName) == MODE_ALLOWED) {
- return;
- }
- // Otherwise, you need a permission...
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), callingUid, null);
- }
-
- /**
- * Verify that historical appop request arguments are valid.
- */
- private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
- String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
- long endTimeMillis, int flags) {
- if ((filter & FILTER_BY_UID) != 0) {
- Preconditions.checkArgument(uid != Process.INVALID_UID);
- } else {
- Preconditions.checkArgument(uid == Process.INVALID_UID);
- }
-
- if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
- Objects.requireNonNull(packageName);
- } else {
- Preconditions.checkArgument(packageName == null);
- }
-
- if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
- Preconditions.checkArgument(attributionTag == null);
- }
-
- if ((filter & FILTER_BY_OP_NAMES) != 0) {
- Objects.requireNonNull(opNames);
- } else {
- Preconditions.checkArgument(opNames == null);
- }
-
- Preconditions.checkFlagsArgument(filter,
- FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
- | FILTER_BY_OP_NAMES);
- Preconditions.checkArgumentNonnegative(beginTimeMillis);
- Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
- Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+ return mAppOpsService.getOpsForPackage(uid, packageName, ops);
}
@Override
public void getHistoricalOps(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
- PackageManager pm = mContext.getPackageManager();
-
- ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
- beginTimeMillis, endTimeMillis, flags);
- Objects.requireNonNull(callback, "callback cannot be null");
- ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
- if (!isSelfRequest) {
- boolean isCallerInstrumented =
- ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
- boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
- boolean isCallerPermissionController;
- try {
- isCallerPermissionController = pm.getPackageUidAsUser(
- mContext.getPackageManager().getPermissionControllerPackageName(), 0,
- UserHandle.getUserId(Binder.getCallingUid()))
- == Binder.getCallingUid();
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- return;
- }
-
- boolean doesCallerHavePermission = mContext.checkPermission(
- android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid())
- == PackageManager.PERMISSION_GRANTED;
-
- if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
- && !doesCallerHavePermission) {
- mHandler.post(() -> callback.sendResult(new Bundle()));
- return;
- }
-
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- }
-
- final String[] opNamesArray = (opNames != null)
- ? opNames.toArray(new String[opNames.size()]) : null;
-
- Set<String> attributionChainExemptPackages = null;
- if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
- attributionChainExemptPackages =
- PermissionManager.getIndicatorExemptedPackages(mContext);
- }
-
- final String[] chainExemptPkgArray = attributionChainExemptPackages != null
- ? attributionChainExemptPackages.toArray(
- new String[attributionChainExemptPackages.size()]) : null;
-
- // Must not hold the appops lock
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
- mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
- filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
- callback).recycleOnUse());
+ mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames,
+ dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
}
@Override
public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
- ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
- beginTimeMillis, endTimeMillis, flags);
- Objects.requireNonNull(callback, "callback cannot be null");
-
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-
- final String[] opNamesArray = (opNames != null)
- ? opNames.toArray(new String[opNames.size()]) : null;
-
- Set<String> attributionChainExemptPackages = null;
- if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
- attributionChainExemptPackages =
- PermissionManager.getIndicatorExemptedPackages(mContext);
- }
-
- final String[] chainExemptPkgArray = attributionChainExemptPackages != null
- ? attributionChainExemptPackages.toArray(
- new String[attributionChainExemptPackages.size()]) : null;
-
- // Must not hold the appops lock
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
- mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
- filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
- callback).recycleOnUse());
+ mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag,
+ opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
}
@Override
public void reloadNonHistoricalState() {
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
- writeState();
- readState();
+ mAppOpsService.reloadNonHistoricalState();
}
@Override
public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState == null) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
- if (resOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- null, uidState.uid, resOps);
- res.add(resPackage);
- return res;
- }
- }
-
- private void pruneOpLocked(Op op, int uid, String packageName) {
- op.removeAttributionsWithNoTime();
-
- if (op.mAttributions.isEmpty()) {
- Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
- if (ops != null) {
- ops.remove(op.op);
- op.setMode(AppOpsManager.opToDefaultMode(op.op));
- if (ops.size() <= 0) {
- UidState uidState = ops.uidState;
- ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
- if (pkgOps != null) {
- pkgOps.remove(ops.packageName);
- mAppOpsServiceInterface.removePackage(ops.packageName,
- UserHandle.getUserId(uidState.uid));
- if (pkgOps.isEmpty()) {
- uidState.pkgOps = null;
- }
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uid);
- }
- }
- }
- }
- }
- }
-
- private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
- if (callingPid == Process.myPid()) {
- return;
- }
- final int callingUser = UserHandle.getUserId(callingUid);
- synchronized (this) {
- if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
- if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
- // Profile owners are allowed to change modes but only for apps
- // within their user.
- return;
- }
- }
- }
- mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
+ return mAppOpsService.getUidOps(uid, ops);
}
@Override
public void setUidMode(int code, int uid, int mode) {
- setUidMode(code, uid, mode, null);
- }
-
- private void setUidMode(int code, int uid, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback) {
- if (DEBUG) {
- Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
- + " by uid " + Binder.getCallingUid());
- }
-
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
- verifyIncomingOp(code);
- code = AppOpsManager.opToSwitch(code);
-
- if (permissionPolicyCallback == null) {
- updatePermissionRevokedCompat(uid, code, mode);
- }
-
- int previousMode;
- synchronized (this) {
- final int defaultMode = AppOpsManager.opToDefaultMode(code);
-
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState == null) {
- if (mode == defaultMode) {
- return;
- }
- uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- }
- if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
- previousMode = uidState.getUidMode(code);
- } else {
- // doesn't look right but is legacy behavior.
- previousMode = MODE_DEFAULT;
- }
-
- if (!uidState.setUidMode(code, mode)) {
- return;
- }
- uidState.evalForegroundOps();
- if (mode != MODE_ERRORED && mode != previousMode) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
- }
- }
-
- notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
- notifyOpChangedSync(code, uid, null, mode, previousMode);
- }
-
- /**
- * Notify that an op changed for all packages in an uid.
- *
- * @param code The op that changed
- * @param uid The uid the op was changed for
- * @param onlyForeground Only notify watchers that watch for foreground changes
- */
- private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
- @Nullable IAppOpsCallback callbackToIgnore) {
- ModeCallback listenerToIgnore = callbackToIgnore != null
- ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
- mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
- listenerToIgnore);
- }
-
- private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
- PackageManager packageManager = mContext.getPackageManager();
- if (packageManager == null) {
- // This can only happen during early boot. At this time the permission state and appop
- // state are in sync
- return;
- }
-
- String[] packageNames = packageManager.getPackagesForUid(uid);
- if (ArrayUtils.isEmpty(packageNames)) {
- return;
- }
- String packageName = packageNames[0];
-
- int[] ops = mSwitchedOps.get(switchCode);
- for (int code : ops) {
- String permissionName = AppOpsManager.opToPermission(code);
- if (permissionName == null) {
- continue;
- }
-
- if (packageManager.checkPermission(permissionName, packageName)
- != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
-
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- continue;
- }
-
- if (!permissionInfo.isRuntime()) {
- continue;
- }
-
- boolean supportsRuntimePermissions = getPackageManagerInternal()
- .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
-
- UserHandle user = UserHandle.getUserHandleForUid(uid);
- boolean isRevokedCompat;
- if (permissionInfo.backgroundPermission != null) {
- if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
- == PackageManager.PERMISSION_GRANTED) {
- boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-
- if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
- Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
- + " permission state, this is discouraged and you should revoke the"
- + " runtime permission instead: uid=" + uid + ", switchCode="
- + switchCode + ", mode=" + mode + ", permission="
- + permissionInfo.backgroundPermission);
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
- packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
- isBackgroundRevokedCompat
- ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
- && mode != AppOpsManager.MODE_FOREGROUND;
- } else {
- isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
- }
-
- if (isRevokedCompat && supportsRuntimePermissions) {
- Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
- + " permission state, this is discouraged and you should revoke the"
- + " runtime permission instead: uid=" + uid + ", switchCode="
- + switchCode + ", mode=" + mode + ", permission=" + permissionName);
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- packageManager.updatePermissionFlags(permissionName, packageName,
- PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
- ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
- int previousMode) {
- final StorageManagerInternal storageManagerInternal =
- LocalServices.getService(StorageManagerInternal.class);
- if (storageManagerInternal != null) {
- storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
- }
+ mAppOpsService.setUidMode(code, uid, mode, null);
}
/**
@@ -1866,309 +407,12 @@
*/
@Override
public void setMode(int code, int uid, @NonNull String packageName, int mode) {
- setMode(code, uid, packageName, mode, null);
- }
-
- private void setMode(int code, int uid, @NonNull String packageName, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback) {
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return;
- }
-
- ArraySet<OnOpModeChangedListener> repCbs = null;
- code = AppOpsManager.opToSwitch(code);
-
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, null);
- } catch (SecurityException e) {
- Slog.e(TAG, "Cannot setMode", e);
- return;
- }
-
- int previousMode = MODE_DEFAULT;
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, false);
- Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
- if (op != null) {
- if (op.getMode() != mode) {
- previousMode = op.getMode();
- op.setMode(mode);
-
- if (uidState != null) {
- uidState.evalForegroundOps();
- }
- ArraySet<OnOpModeChangedListener> cbs =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (cbs != null) {
- if (repCbs == null) {
- repCbs = new ArraySet<>();
- }
- repCbs.addAll(cbs);
- }
- cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
- if (cbs != null) {
- if (repCbs == null) {
- repCbs = new ArraySet<>();
- }
- repCbs.addAll(cbs);
- }
- if (repCbs != null && permissionPolicyCallback != null) {
- repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
- }
- if (mode == AppOpsManager.opToDefaultMode(op.op)) {
- // If going into the default mode, prune this op
- // if there is nothing else interesting in it.
- pruneOpLocked(op, uid, packageName);
- }
- scheduleFastWriteLocked();
- if (mode != MODE_ERRORED) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
- }
- }
- }
- }
- if (repCbs != null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, repCbs, code, uid, packageName));
- }
-
- notifyOpChangedSync(code, uid, packageName, mode, previousMode);
- }
-
- private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
- int uid, String packageName) {
- for (int i = 0; i < callbacks.size(); i++) {
- final OnOpModeChangedListener callback = callbacks.valueAt(i);
- notifyOpChanged(callback, code, uid, packageName);
- }
- }
-
- private void notifyOpChanged(OnOpModeChangedListener callback, int code,
- int uid, String packageName) {
- mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
- }
-
- private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
- int op, int uid, String packageName, int previousMode) {
- boolean duplicate = false;
- if (reports == null) {
- reports = new ArrayList<>();
- } else {
- final int reportCount = reports.size();
- for (int j = 0; j < reportCount; j++) {
- ChangeRec report = reports.get(j);
- if (report.op == op && report.pkg.equals(packageName)) {
- duplicate = true;
- break;
- }
- }
- }
- if (!duplicate) {
- reports.add(new ChangeRec(op, uid, packageName, previousMode));
- }
-
- return reports;
- }
-
- private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
- HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
- int op, int uid, String packageName, int previousMode,
- ArraySet<OnOpModeChangedListener> cbs) {
- if (cbs == null) {
- return callbacks;
- }
- if (callbacks == null) {
- callbacks = new HashMap<>();
- }
- final int N = cbs.size();
- for (int i=0; i<N; i++) {
- OnOpModeChangedListener cb = cbs.valueAt(i);
- ArrayList<ChangeRec> reports = callbacks.get(cb);
- ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
- if (changed != reports) {
- callbacks.put(cb, changed);
- }
- }
- return callbacks;
- }
-
- static final class ChangeRec {
- final int op;
- final int uid;
- final String pkg;
- final int previous_mode;
-
- ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
- op = _op;
- uid = _uid;
- pkg = _pkg;
- previous_mode = _previous_mode;
- }
+ mAppOpsService.setMode(code, uid, packageName, mode, null);
}
@Override
public void resetAllModes(int reqUserId, String reqPackageName) {
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
- reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
- true, true, "resetAllModes", null);
-
- int reqUid = -1;
- if (reqPackageName != null) {
- try {
- reqUid = AppGlobals.getPackageManager().getPackageUid(
- reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
- } catch (RemoteException e) {
- /* ignore - local call */
- }
- }
-
- enforceManageAppOpsModes(callingPid, callingUid, reqUid);
-
- HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
- ArrayList<ChangeRec> allChanges = new ArrayList<>();
- synchronized (this) {
- boolean changed = false;
- for (int i = mUidStates.size() - 1; i >= 0; i--) {
- UidState uidState = mUidStates.valueAt(i);
-
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
- final int uidOpCount = opModes.size();
- for (int j = uidOpCount - 1; j >= 0; j--) {
- final int code = opModes.keyAt(j);
- if (AppOpsManager.opAllowsReset(code)) {
- int previousMode = opModes.valueAt(j);
- uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
- for (String packageName : getPackagesForUid(uidState.uid)) {
- callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
- previousMode,
- mAppOpsServiceInterface.getOpModeChangedListeners(code));
- callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
- previousMode, mAppOpsServiceInterface
- .getPackageModeChangedListeners(packageName));
-
- allChanges = addChange(allChanges, code, uidState.uid,
- packageName, previousMode);
- }
- }
- }
- }
-
- if (uidState.pkgOps == null) {
- continue;
- }
-
- if (reqUserId != UserHandle.USER_ALL
- && reqUserId != UserHandle.getUserId(uidState.uid)) {
- // Skip any ops for a different user
- continue;
- }
-
- Map<String, Ops> packages = uidState.pkgOps;
- Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
- boolean uidChanged = false;
- while (it.hasNext()) {
- Map.Entry<String, Ops> ent = it.next();
- String packageName = ent.getKey();
- if (reqPackageName != null && !reqPackageName.equals(packageName)) {
- // Skip any ops for a different package
- continue;
- }
- Ops pkgOps = ent.getValue();
- for (int j=pkgOps.size()-1; j>=0; j--) {
- Op curOp = pkgOps.valueAt(j);
- if (shouldDeferResetOpToDpm(curOp.op)) {
- deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
- continue;
- }
- if (AppOpsManager.opAllowsReset(curOp.op)
- && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
- int previousMode = curOp.getMode();
- curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
- changed = true;
- uidChanged = true;
- final int uid = curOp.uidState.uid;
- callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode,
- mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
- callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode, mAppOpsServiceInterface
- .getPackageModeChangedListeners(packageName));
-
- allChanges = addChange(allChanges, curOp.op, uid, packageName,
- previousMode);
- curOp.removeAttributionsWithNoTime();
- if (curOp.mAttributions.isEmpty()) {
- pkgOps.removeAt(j);
- }
- }
- }
- if (pkgOps.size() == 0) {
- it.remove();
- mAppOpsServiceInterface.removePackage(packageName,
- UserHandle.getUserId(uidState.uid));
- }
- }
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uidState.uid);
- }
- if (uidChanged) {
- uidState.evalForegroundOps();
- }
- }
-
- if (changed) {
- scheduleFastWriteLocked();
- }
- }
- if (callbacks != null) {
- for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
- : callbacks.entrySet()) {
- OnOpModeChangedListener cb = ent.getKey();
- ArrayList<ChangeRec> reports = ent.getValue();
- for (int i=0; i<reports.size(); i++) {
- ChangeRec rep = reports.get(i);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, cb, rep.op, rep.uid, rep.pkg));
- }
- }
- }
-
- int numChanges = allChanges.size();
- for (int i = 0; i < numChanges; i++) {
- ChangeRec change = allChanges.get(i);
- notifyOpChangedSync(change.op, change.uid, change.pkg,
- AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
- }
- }
-
- private boolean shouldDeferResetOpToDpm(int op) {
- // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
- // pre-grants to a role-based mechanism or another general-purpose mechanism.
- return dpmi != null && dpmi.supportsResetOp(op);
- }
-
- /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
- private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
- // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
- // pre-grants to a role-based mechanism or another general-purpose mechanism.
- dpmi.resetOp(op, packageName, userId);
- }
-
- private void evalAllForegroundOpsLocked() {
- for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
- final UidState uidState = mUidStates.valueAt(uidi);
- if (uidState.foregroundOps != null) {
- uidState.evalForegroundOps();
- }
- }
+ mAppOpsService.resetAllModes(reqUserId, reqPackageName);
}
@Override
@@ -2179,66 +423,17 @@
@Override
public void startWatchingModeWithFlags(int op, String packageName, int flags,
IAppOpsCallback callback) {
- int watchedUid = -1;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- // TODO: should have a privileged permission to protect this.
- // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
- // the USAGE_STATS permission since this can provide information about when an
- // app is in the foreground?
- Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
- AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
- if (callback == null) {
- return;
- }
- final boolean mayWatchPackageName = packageName != null
- && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
- synchronized (this) {
- int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
-
- int notifiedOps;
- if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
- if (op == OP_NONE) {
- notifiedOps = ALL_OPS;
- } else {
- notifiedOps = op;
- }
- } else {
- notifiedOps = switchOp;
- }
-
- ModeCallback cb = mModeWatchers.get(callback.asBinder());
- if (cb == null) {
- cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
- callingPid);
- mModeWatchers.put(callback.asBinder(), cb);
- }
- if (switchOp != AppOpsManager.OP_NONE) {
- mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
- }
- if (mayWatchPackageName) {
- mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
- }
- evalAllForegroundOpsLocked();
- }
+ mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback);
}
@Override
public void stopWatchingMode(IAppOpsCallback callback) {
- if (callback == null) {
- return;
- }
- synchronized (this) {
- ModeCallback cb = mModeWatchers.remove(callback.asBinder());
- if (cb != null) {
- cb.unlinkToDeath();
- mAppOpsServiceInterface.removeListener(cb);
- }
-
- evalAllForegroundOpsLocked();
- }
+ mAppOpsService.stopWatchingMode(callback);
}
+ /**
+ * @return the current {@link CheckOpsDelegate}.
+ */
public CheckOpsDelegate getAppOpsServiceDelegate() {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
@@ -2246,6 +441,9 @@
}
}
+ /**
+ * Sets the appops {@link CheckOpsDelegate}
+ */
public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
@@ -2269,58 +467,7 @@
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, boolean raw) {
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return AppOpsManager.opToDefaultMode(code);
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
- }
-
- /**
- * Get the mode of an app-op.
- *
- * @param code The code of the op
- * @param uid The uid of the package the op belongs to
- * @param packageName The package the op belongs to
- * @param raw If the raw state of eval-ed state should be checked.
- *
- * @return The mode of the op
- */
- private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean raw) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, null);
- } catch (SecurityException e) {
- Slog.e(TAG, "checkOperation", e);
- return AppOpsManager.opToDefaultMode(code);
- }
-
- if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
- return AppOpsManager.MODE_IGNORED;
- }
- synchronized (this) {
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
- return AppOpsManager.MODE_IGNORED;
- }
- code = AppOpsManager.opToSwitch(code);
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState != null
- && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
- final int rawMode = uidState.getUidMode(code);
- return raw ? rawMode : uidState.evalMode(code, rawMode);
- }
- Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
- if (op == null) {
- return AppOpsManager.opToDefaultMode(code);
- }
- return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
- }
+ return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw);
}
@Override
@@ -2340,7 +487,8 @@
@Override
public void setAudioRestriction(int code, int usage, int uid, int mode,
String[] exceptionPackages) {
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+ Binder.getCallingUid(), uid);
verifyIncomingUid(uid);
verifyIncomingOp(code);
@@ -2348,58 +496,35 @@
code, usage, uid, mode, exceptionPackages);
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code,
+ UID_ANY));
}
@Override
public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
+ mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+ Binder.getCallingUid(), -1);
mAudioRestrictionManager.setCameraAudioRestriction(mode);
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
AppOpsManager.OP_VIBRATE, UID_ANY));
}
@Override
public int checkPackage(int uid, String packageName) {
- Objects.requireNonNull(packageName);
- try {
- verifyAndGetBypass(uid, packageName, null);
- // When the caller is the system, it's possible that the packageName is the special
- // one (e.g., "root") which isn't actually existed.
- if (resolveUid(packageName) == uid
- || (isPackageExisted(packageName)
- && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
- return AppOpsManager.MODE_ALLOWED;
- }
- return AppOpsManager.MODE_ERRORED;
- } catch (SecurityException ignored) {
- return AppOpsManager.MODE_ERRORED;
- }
+ return mAppOpsService.checkPackage(uid, packageName);
}
private boolean isPackageExisted(String packageName) {
return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
}
- /**
- * This method will check with PackageManager to determine if the package provided should
- * be visible to the {@link Binder#getCallingUid()}.
- *
- * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
- */
- private boolean filterAppAccessUnlocked(String packageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- return LocalServices.getService(PackageManagerInternal.class)
- .filterAppAccess(packageName, callingUid, userId);
- }
-
@Override
public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
@@ -2445,13 +570,20 @@
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
+ final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
- if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
- return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
+ proxyFlags);
+ if (proxyReturn != AppOpsManager.MODE_ALLOWED) {
+ return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag,
proxiedPackageName);
}
+ if (shouldCollectAsyncNotedOp) {
+ boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+ resolveProxyPackageName, proxyAttributionTag, null);
+ collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code,
+ isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+ message, shouldCollectMessage);
+ }
}
String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -2463,9 +595,32 @@
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
- return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
- proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid,
+ resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxiedFlags);
+
+ boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+ resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName);
+ if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) {
+ collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags,
+ message, shouldCollectMessage);
+ }
+
+
+ return new SyncNotedAppOp(result, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ resolveProxiedPackageName);
+ }
+
+ private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
+ if (attributionSource.getUid() != Binder.getCallingUid()
+ && attributionSource.isTrusted(mContext)) {
+ return true;
+ }
+ return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null)
+ == PackageManager.PERMISSION_GRANTED;
}
@Override
@@ -2479,258 +634,58 @@
private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ int result = mAppOpsService.noteOperation(code, uid, packageName,
+ attributionTag, message);
+
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
- }
- return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
- }
- private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- boolean wasNull = attributionTag == null;
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "noteOperation", e);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
+ boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+ resolvedPackageName, attributionTag, null);
+
+ if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+ collectAsyncNotedOp(uid, resolvedPackageName, code,
+ isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+ message, shouldCollectMessage);
}
- synchronized (this) {
- final Ops ops = getOpsLocked(uid, packageName, attributionTag,
- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
- if (ops == null) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
- if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
- + " package " + packageName + "flags: " +
- AppOpsManager.flagsToString(flags));
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
- final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
- if (attributedOp.isRunning()) {
- Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
- + code + " startTime of in progress event="
- + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
- }
-
- final int switchCode = AppOpsManager.opToSwitch(code);
- final UidState uidState = ops.uidState;
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
- }
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
- final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
- if (uidMode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- uidMode);
- return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
- }
- } else {
- final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
- : op;
- final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- mode);
- return new SyncNotedAppOp(mode, code, attributionTag, packageName);
- }
- }
- if (DEBUG) {
- Slog.d(TAG,
- "noteOperation: allowing code " + code + " uid " + uid + " package "
- + packageName + (attributionTag == null ? ""
- : "." + attributionTag) + " flags: "
- + AppOpsManager.flagsToString(flags));
- }
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_ALLOWED);
- attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(),
- flags);
-
- if (shouldCollectAsyncNotedOp) {
- collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
- shouldCollectMessage);
- }
-
- return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
- packageName);
- }
+ return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+ resolvedPackageName);
}
// TODO moltmann: Allow watching for attribution ops
@Override
public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
- if (ops != null) {
- Preconditions.checkArrayElementsInRange(ops, 0,
- AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
- }
- if (callback == null) {
- return;
- }
- synchronized (this) {
- SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mActiveWatchers.put(callback.asBinder(), callbacks);
- }
- final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, activeCallback);
- }
- }
+ mAppOpsService.startWatchingActive(ops, callback);
}
@Override
public void stopWatchingActive(IAppOpsActiveCallback callback) {
- if (callback == null) {
- return;
- }
- synchronized (this) {
- final SparseArray<ActiveCallback> activeCallbacks =
- mActiveWatchers.remove(callback.asBinder());
- if (activeCallbacks == null) {
- return;
- }
- final int callbackCount = activeCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- activeCallbacks.valueAt(i).destroy();
- }
- }
+ mAppOpsService.stopWatchingActive(callback);
}
@Override
public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
-
- Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
- Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
- "Invalid op code in: " + Arrays.toString(ops));
- Objects.requireNonNull(callback, "Callback cannot be null");
-
- synchronized (this) {
- SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mStartedWatchers.put(callback.asBinder(), callbacks);
- }
-
- final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, startedCallback);
- }
- }
+ mAppOpsService.startWatchingStarted(ops, callback);
}
@Override
public void stopWatchingStarted(IAppOpsStartedCallback callback) {
- Objects.requireNonNull(callback, "Callback cannot be null");
-
- synchronized (this) {
- final SparseArray<StartedCallback> startedCallbacks =
- mStartedWatchers.remove(callback.asBinder());
- if (startedCallbacks == null) {
- return;
- }
-
- final int callbackCount = startedCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- startedCallbacks.valueAt(i).destroy();
- }
- }
+ mAppOpsService.stopWatchingStarted(callback);
}
@Override
public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
- Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
- Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
- "Invalid op code in: " + Arrays.toString(ops));
- Objects.requireNonNull(callback, "Callback cannot be null");
- synchronized (this) {
- SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mNotedWatchers.put(callback.asBinder(), callbacks);
- }
- final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, notedCallback);
- }
- }
+ mAppOpsService.startWatchingNoted(ops, callback);
}
@Override
public void stopWatchingNoted(IAppOpsNotedCallback callback) {
- Objects.requireNonNull(callback, "Callback cannot be null");
- synchronized (this) {
- final SparseArray<NotedCallback> notedCallbacks =
- mNotedWatchers.remove(callback.asBinder());
- if (notedCallbacks == null) {
- return;
- }
- final int callbackCount = notedCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- notedCallbacks.valueAt(i).destroy();
- }
- }
+ mAppOpsService.stopWatchingNoted(callback);
}
/**
@@ -2817,7 +772,7 @@
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
- verifyAndGetBypass(uid, packageName, null);
+ mAppOpsService.verifyPackage(uid, packageName);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2847,7 +802,7 @@
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
- verifyAndGetBypass(uid, packageName, null);
+ mAppOpsService.verifyPackage(uid, packageName);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2866,7 +821,7 @@
int uid = Binder.getCallingUid();
- verifyAndGetBypass(uid, packageName, null);
+ mAppOpsService.verifyPackage(uid, packageName);
synchronized (this) {
return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -2889,54 +844,49 @@
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ int result = mAppOpsService.startOperation(clientId, code, uid, packageName,
+ attributionTag, startIfModeDefault, message,
+ attributionFlags, attributionChainId);
+
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
+
+ boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+ resolvedPackageName, attributionTag, null);
+
+ if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+ collectAsyncNotedOp(uid, resolvedPackageName, code,
+ isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+ message, shouldCollectMessage);
}
- // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
- // purposes and not as a check, also make sure that the caller is allowed to access
- // the data gated by OP_RECORD_AUDIO.
- //
- // TODO: Revert this change before Android 12.
- if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
- int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
- if (result != AppOpsManager.MODE_ALLOWED) {
- return new SyncNotedAppOp(result, code, attributionTag, packageName);
- }
- }
- return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
- attributionChainId, /*dryRun*/ false);
+ return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+ resolvedPackageName);
}
@Override
- public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
- return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
- attributionChainId);
+ return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code,
+ attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
}
- private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
+ private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
int attributionChainId) {
+
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
@@ -2984,147 +934,68 @@
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
+ final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
- if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
- return testProxiedOp;
+
+ boolean isTestProxiedAttributionTagValid =
+ mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName,
+ proxiedAttributionTag, resolvedProxyPackageName);
+
+ if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) {
+ return new SyncNotedAppOp(testProxiedOp, code,
+ isTestProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ resolvedProxiedPackageName);
}
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
+ final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
- shouldCollectMessage, proxyAttributionFlags, attributionChainId,
+ proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId,
/*dryRun*/ false);
- if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
- return proxyAppOp;
+
+ boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, null);
+
+ if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) {
+ return new SyncNotedAppOp(proxyAppOp, code,
+ isProxyAttributionTagValid ? proxyAttributionTag : null,
+ resolvedProxyPackageName);
+ }
+
+ if (shouldCollectAsyncNotedOp) {
+ collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code,
+ isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+ message, shouldCollectMessage);
}
}
- return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
- proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
- /*dryRun*/ false);
+ final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+ proxiedAttributionFlags, attributionChainId,/*dryRun*/ false);
+
+ boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName);
+
+ if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) {
+ collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ proxiedAttributionFlags, message, shouldCollectMessage);
+ }
+
+ return new SyncNotedAppOp(proxiedAppOp, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ resolvedProxiedPackageName);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
}
- private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
- int attributionChainId, boolean dryRun) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "startOperation", e);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
-
- boolean isRestricted = false;
- int startType = START_TYPE_FAILED;
- synchronized (this) {
- final Ops ops = getOpsLocked(uid, packageName, attributionTag,
- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
- if (ops == null) {
- if (!dryRun) {
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
- }
- if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
- + " package " + packageName + " flags: "
- + AppOpsManager.flagsToString(flags));
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
- final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
- final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
- final int switchCode = AppOpsManager.opToSwitch(code);
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
- final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
- if (!shouldStartForMode(uidMode, startIfModeDefault)) {
- if (DEBUG) {
- Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- }
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
- }
- return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
- }
- } else {
- final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
- : op;
- final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
- if (mode != AppOpsManager.MODE_ALLOWED
- && (!startIfModeDefault || mode != MODE_DEFAULT)) {
- if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
- }
- return new SyncNotedAppOp(mode, code, attributionTag, packageName);
- }
- }
- if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
- + " package " + packageName + " restricted: " + isRestricted
- + " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- try {
- if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- } else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- startType = START_TYPE_STARTED;
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
- attributionChainId);
- }
- }
-
- if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
- collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
- message, shouldCollectMessage);
- }
-
- return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
- packageName);
- }
-
@Override
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
@@ -3134,22 +1005,11 @@
private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return;
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return;
- }
-
- finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag);
}
@Override
- public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ public void finishProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation);
@@ -3181,8 +1041,8 @@
}
if (!skipProxyOperation) {
- finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag);
+ mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag);
}
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3191,209 +1051,12 @@
return null;
}
- finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag);
+ mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag);
return null;
}
- private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
- String attributionTag) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag);
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "Cannot finishOperation", e);
- return;
- }
-
- synchronized (this) {
- Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
- pvr.bypass, /* edit */ true);
- if (op == null) {
- Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- return;
- }
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
- if (attributedOp == null) {
- Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- return;
- }
-
- if (attributedOp.isRunning() || attributedOp.isPaused()) {
- attributedOp.finished(clientId);
- } else {
- Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- }
- }
- }
-
- void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
- String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
- ArraySet<ActiveCallback> dispatchedCallbacks = null;
- final int callbackListCount = mActiveWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
- ActiveCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
- if (dispatchedCallbacks == null) {
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpActiveChanged,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
- attributionFlags, attributionChainId));
- }
-
- private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
- int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
- boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
- // There are features watching for mode changes such as window manager
- // and location manager which are in our process. The callbacks in these
- // features may require permissions our remote caller does not have.
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final ActiveCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
- active, attributionFlags, attributionChainId);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result,
- @AppOpsManager.OnOpStartedListener.StartedType int startedType,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- ArraySet<StartedCallback> dispatchedCallbacks = null;
- final int callbackListCount = mStartedWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
-
- StartedCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
-
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
-
- if (dispatchedCallbacks == null) {
- return;
- }
-
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpStarted,
- this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId));
- }
-
- private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final StartedCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
- ArraySet<NotedCallback> dispatchedCallbacks = null;
- final int callbackListCount = mNotedWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
- final NotedCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
- if (dispatchedCallbacks == null) {
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChecked,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
- result));
- }
-
- private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
- // There are features watching for checks in our process. The callbacks in
- // these features may require permissions our remote caller does not have.
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final NotedCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
- result);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
@Override
public int permissionToOpCode(String permission) {
if (permission == null) {
@@ -3451,13 +1114,6 @@
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
- private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
- // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
- // as watcher should not use this to signal if the value is changed.
- return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
- watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
- }
-
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
// Enforce manage appops permission if it's a restricted read op.
@@ -3498,35 +1154,6 @@
|| resolveUid(resolvedPackage) != Process.INVALID_UID;
}
- private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
- if (attributionSource.getUid() != Binder.getCallingUid()
- && attributionSource.isTrusted(mContext)) {
- return true;
- }
- return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null) {
- if (!edit) {
- return null;
- }
- uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- }
-
- return uidState;
- }
-
- private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
- synchronized (this) {
- getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
- }
- }
-
/**
* @return {@link PackageManagerInternal}
*/
@@ -3538,764 +1165,6 @@
return mPackageManagerInternal;
}
- /**
- * Create a restriction description matching the properties of the package.
- *
- * @param pkg The package to create the restriction description for
- *
- * @return The restriction matching the package
- */
- private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
- return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
- mContext.checkPermission(android.Manifest.permission
- .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
- == PackageManager.PERMISSION_GRANTED);
- }
-
- /**
- * @see #verifyAndGetBypass(int, String, String, String)
- */
- private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag) {
- return verifyAndGetBypass(uid, packageName, attributionTag, null);
- }
-
- /**
- * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
- * description} for the package, along with a boolean indicating whether the attribution tag is
- * valid.
- *
- * @param uid The uid the package belongs to
- * @param packageName The package the might belong to the uid
- * @param attributionTag attribution tag or {@code null} if no need to verify
- * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
- *
- * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
- * attribution tag is valid
- */
- private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag, @Nullable String proxyPackageName) {
- if (uid == Process.ROOT_UID) {
- // For backwards compatibility, don't check package name for root UID.
- return new PackageVerificationResult(null,
- /* isAttributionTagValid */ true);
- }
- if (Process.isSdkSandboxUid(uid)) {
- // SDK sandbox processes run in their own UID range, but their associated
- // UID for checks should always be the UID of the package implementing SDK sandbox
- // service.
- // TODO: We will need to modify the callers of this function instead, so
- // modifications and checks against the app ops state are done with the
- // correct UID.
- try {
- final PackageManager pm = mContext.getPackageManager();
- final String supplementalPackageName = pm.getSdkSandboxPackageName();
- if (Objects.equals(packageName, supplementalPackageName)) {
- uid = pm.getPackageUidAsUser(supplementalPackageName,
- PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Shouldn't happen for the supplemental package
- e.printStackTrace();
- }
- }
-
-
- // Do not check if uid/packageName/attributionTag is already known.
- synchronized (this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState != null && uidState.pkgOps != null) {
- Ops ops = uidState.pkgOps.get(packageName);
-
- if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
- attributionTag)) && ops.bypass != null) {
- return new PackageVerificationResult(ops.bypass,
- ops.validAttributionTags.contains(attributionTag));
- }
- }
- }
-
- int callingUid = Binder.getCallingUid();
-
- // Allow any attribution tag for resolvable uids
- int pkgUid;
- if (Objects.equals(packageName, "com.android.shell")) {
- // Special case for the shell which is a package but should be able
- // to bypass app attribution tag restrictions.
- pkgUid = Process.SHELL_UID;
- } else {
- pkgUid = resolveUid(packageName);
- }
- if (pkgUid != Process.INVALID_UID) {
- if (pkgUid != UserHandle.getAppId(uid)) {
- Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
- + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
- String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
- throw new SecurityException("Specified package \"" + packageName + "\" under uid "
- + UserHandle.getAppId(uid) + otherUidMessage);
- }
- return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
- /* isAttributionTagValid */ true);
- }
-
- int userId = UserHandle.getUserId(uid);
- RestrictionBypass bypass = null;
- boolean isAttributionTagValid = false;
-
- final long ident = Binder.clearCallingIdentity();
- try {
- PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
- AndroidPackage pkg = pmInt.getPackage(packageName);
- if (pkg != null) {
- isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
- pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
- bypass = getBypassforPackage(pkg);
- }
- if (!isAttributionTagValid) {
- AndroidPackage proxyPkg = proxyPackageName != null
- ? pmInt.getPackage(proxyPackageName) : null;
- // Re-check in proxy.
- isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
- String msg;
- if (pkg != null && isAttributionTagValid) {
- msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
- + " package " + proxyPackageName + ", this is not advised";
- } else if (pkg != null) {
- msg = "attributionTag " + attributionTag + " not declared in manifest of "
- + packageName;
- } else {
- msg = "package " + packageName + " not found, can't check for "
- + "attributionTag " + attributionTag;
- }
-
- try {
- if (!mPlatformCompat.isChangeEnabledByPackageName(
- SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
- userId) || !mPlatformCompat.isChangeEnabledByUid(
- SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
- callingUid)) {
- // Do not override tags if overriding is not enabled for this package
- isAttributionTagValid = true;
- }
- Slog.e(TAG, msg);
- } catch (RemoteException neverHappens) {
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- if (pkgUid != uid) {
- Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
- + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
- String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
- throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
- + otherUidMessage);
- }
-
- return new PackageVerificationResult(bypass, isAttributionTagValid);
- }
-
- private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
- @Nullable String attributionTag) {
- if (pkg == null) {
- return false;
- } else if (attributionTag == null) {
- return true;
- }
- if (pkg.getAttributions() != null) {
- int numAttributions = pkg.getAttributions().size();
- for (int i = 0; i < numAttributions; i++) {
- if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Get (and potentially create) ops.
- *
- * @param uid The uid the package belongs to
- * @param packageName The name of the package
- * @param attributionTag attribution tag
- * @param isAttributionTagValid whether the given attribution tag is valid
- * @param bypass When to bypass certain op restrictions (can be null if edit == false)
- * @param edit If an ops does not exist, create the ops?
-
- * @return The ops
- */
- private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
- boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
- UidState uidState = getUidStateLocked(uid, edit);
- if (uidState == null) {
- return null;
- }
-
- if (uidState.pkgOps == null) {
- if (!edit) {
- return null;
- }
- uidState.pkgOps = new ArrayMap<>();
- }
-
- Ops ops = uidState.pkgOps.get(packageName);
- if (ops == null) {
- if (!edit) {
- return null;
- }
- ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
- }
-
- if (edit) {
- if (bypass != null) {
- ops.bypass = bypass;
- }
-
- if (attributionTag != null) {
- ops.knownAttributionTags.add(attributionTag);
- if (isAttributionTagValid) {
- ops.validAttributionTags.add(attributionTag);
- } else {
- ops.validAttributionTags.remove(attributionTag);
- }
- }
- }
-
- return ops;
- }
-
- @Override
- public void scheduleWriteLocked() {
- if (!mWriteScheduled) {
- mWriteScheduled = true;
- mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
- }
- }
-
- @Override
- public void scheduleFastWriteLocked() {
- if (!mFastWriteScheduled) {
- mWriteScheduled = true;
- mFastWriteScheduled = true;
- mHandler.removeCallbacks(mWriteRunner);
- mHandler.postDelayed(mWriteRunner, 10*1000);
- }
- }
-
- /**
- * Get the state of an op for a uid.
- *
- * @param code The code of the op
- * @param uid The uid the of the package
- * @param packageName The package name for which to get the state for
- * @param attributionTag The attribution tag
- * @param isAttributionTagValid Whether the given attribution tag is valid
- * @param bypass When to bypass certain op restrictions (can be null if edit == false)
- * @param edit Iff {@code true} create the {@link Op} object if not yet created
- *
- * @return The {@link Op state} of the op
- */
- private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean isAttributionTagValid,
- @Nullable RestrictionBypass bypass, boolean edit) {
- Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
- edit);
- if (ops == null) {
- return null;
- }
- return getOpLocked(ops, code, uid, edit);
- }
-
- private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
- Op op = ops.get(code);
- if (op == null) {
- if (!edit) {
- return null;
- }
- op = new Op(ops.uidState, ops.packageName, code, uid);
- ops.put(code, op);
- }
- if (edit) {
- scheduleWriteLocked();
- }
- return op;
- }
-
- private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
- if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
- return false;
- }
- final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
- return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
- }
-
- private boolean isOpRestrictedLocked(int uid, int code, String packageName,
- String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
- int restrictionSetCount = mOpGlobalRestrictions.size();
-
- for (int i = 0; i < restrictionSetCount; i++) {
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
- if (restrictionState.hasRestriction(code)) {
- return true;
- }
- }
-
- int userHandle = UserHandle.getUserId(uid);
- restrictionSetCount = mOpUserRestrictions.size();
-
- for (int i = 0; i < restrictionSetCount; i++) {
- // For each client, check that the given op is not restricted, or that the given
- // package is exempt from the restriction.
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
- if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
- isCheckOp)) {
- RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
- if (opBypass != null) {
- // If we are the system, bypass user restrictions for certain codes
- synchronized (this) {
- if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
- return false;
- }
- if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
- return false;
- }
- if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
- && appBypass.isRecordAudioRestrictionExcept) {
- return false;
- }
- }
- }
- return true;
- }
- }
- return false;
- }
-
- void readState() {
- int oldVersion = NO_VERSION;
- synchronized (mFile) {
- synchronized (this) {
- FileInputStream stream;
- try {
- stream = mFile.openRead();
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
- return;
- }
- boolean success = false;
- mUidStates.clear();
- mAppOpsServiceInterface.clearAllModes();
- try {
- TypedXmlPullParser parser = Xml.resolvePullParser(stream);
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- ;
- }
-
- if (type != XmlPullParser.START_TAG) {
- throw new IllegalStateException("no start tag found");
- }
-
- oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
-
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("pkg")) {
- readPackage(parser);
- } else if (tagName.equals("uid")) {
- readUidOps(parser);
- } else {
- Slog.w(TAG, "Unknown element under <app-ops>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- success = true;
- } catch (IllegalStateException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (NullPointerException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (IOException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (IndexOutOfBoundsException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } finally {
- if (!success) {
- mUidStates.clear();
- mAppOpsServiceInterface.clearAllModes();
- }
- try {
- stream.close();
- } catch (IOException e) {
- }
- }
- }
- }
- synchronized (this) {
- upgradeLocked(oldVersion);
- }
- }
-
- private void upgradeRunAnyInBackgroundLocked() {
- for (int i = 0; i < mUidStates.size(); i++) {
- final UidState uidState = mUidStates.valueAt(i);
- if (uidState == null) {
- continue;
- }
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null) {
- final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
- if (idx >= 0) {
- uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- opModes.valueAt(idx));
- }
- }
- if (uidState.pkgOps == null) {
- continue;
- }
- boolean changed = false;
- for (int j = 0; j < uidState.pkgOps.size(); j++) {
- Ops ops = uidState.pkgOps.valueAt(j);
- if (ops != null) {
- final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
- if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
- final Op copy = new Op(op.uidState, op.packageName,
- AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
- copy.setMode(op.getMode());
- ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
- changed = true;
- }
- }
- }
- if (changed) {
- uidState.evalForegroundOps();
- }
- }
- }
-
- private void upgradeLocked(int oldVersion) {
- if (oldVersion >= CURRENT_VERSION) {
- return;
- }
- Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
- switch (oldVersion) {
- case NO_VERSION:
- upgradeRunAnyInBackgroundLocked();
- // fall through
- case 1:
- // for future upgrades
- }
- scheduleFastWriteLocked();
- }
-
- private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
- XmlPullParserException, IOException {
- final int uid = parser.getAttributeInt(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- final int code = parser.getAttributeInt(null, "n");
- final int mode = parser.getAttributeInt(null, "m");
- setUidMode(code, uid, mode);
- } else {
- Slog.w(TAG, "Unknown element under <uid-ops>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- private void readPackage(TypedXmlPullParser parser)
- throws NumberFormatException, XmlPullParserException, IOException {
- String pkgName = parser.getAttributeValue(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("uid")) {
- readUid(parser, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- private void readUid(TypedXmlPullParser parser, String pkgName)
- throws NumberFormatException, XmlPullParserException, IOException {
- int uid = parser.getAttributeInt(null, "n");
- final UidState uidState = getUidStateLocked(uid, true);
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- readOp(parser, uidState, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- uidState.evalForegroundOps();
- }
-
- private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
- @Nullable String attribution)
- throws NumberFormatException, IOException, XmlPullParserException {
- final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
-
- final long key = parser.getAttributeLong(null, "n");
- final int uidState = extractUidStateFromKey(key);
- final int opFlags = extractFlagsFromKey(key);
-
- final long accessTime = parser.getAttributeLong(null, "t", 0);
- final long rejectTime = parser.getAttributeLong(null, "r", 0);
- final long accessDuration = parser.getAttributeLong(null, "d", -1);
- final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
- final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
- final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
-
- if (accessTime > 0) {
- attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
- proxyAttributionTag, uidState, opFlags);
- }
- if (rejectTime > 0) {
- attributedOp.rejected(rejectTime, uidState, opFlags);
- }
- }
-
- private void readOp(TypedXmlPullParser parser,
- @NonNull UidState uidState, @NonNull String pkgName)
- throws NumberFormatException, XmlPullParserException, IOException {
- int opCode = parser.getAttributeInt(null, "n");
- Op op = new Op(uidState, pkgName, opCode, uidState.uid);
-
- final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
- op.setMode(mode);
-
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tagName = parser.getName();
- if (tagName.equals("st")) {
- readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
- } else {
- Slog.w(TAG, "Unknown element under <op>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
-
- if (uidState.pkgOps == null) {
- uidState.pkgOps = new ArrayMap<>();
- }
- Ops ops = uidState.pkgOps.get(pkgName);
- if (ops == null) {
- ops = new Ops(pkgName, uidState);
- uidState.pkgOps.put(pkgName, ops);
- }
- ops.put(op.op, op);
- }
-
- void writeState() {
- synchronized (mFile) {
- FileOutputStream stream;
- try {
- stream = mFile.startWrite();
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write state: " + e);
- return;
- }
-
- List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
-
- try {
- TypedXmlSerializer out = Xml.resolveSerializer(stream);
- out.startDocument(null, true);
- out.startTag(null, "app-ops");
- out.attributeInt(null, "v", CURRENT_VERSION);
-
- SparseArray<SparseIntArray> uidStatesClone;
- synchronized (this) {
- uidStatesClone = new SparseArray<>(mUidStates.size());
-
- final int uidStateCount = mUidStates.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- UidState uidState = mUidStates.valueAt(uidStateNum);
- int uid = mUidStates.keyAt(uidStateNum);
-
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null && opModes.size() > 0) {
- uidStatesClone.put(uid, opModes);
- }
- }
- }
-
- final int uidStateCount = uidStatesClone.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
- if (opModes != null && opModes.size() > 0) {
- out.startTag(null, "uid");
- out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
- final int opCount = opModes.size();
- for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
- final int op = opModes.keyAt(opCountNum);
- final int mode = opModes.valueAt(opCountNum);
- out.startTag(null, "op");
- out.attributeInt(null, "n", op);
- out.attributeInt(null, "m", mode);
- out.endTag(null, "op");
- }
- out.endTag(null, "uid");
- }
- }
-
- if (allOps != null) {
- String lastPkg = null;
- for (int i=0; i<allOps.size(); i++) {
- AppOpsManager.PackageOps pkg = allOps.get(i);
- if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
- if (lastPkg != null) {
- out.endTag(null, "pkg");
- }
- lastPkg = pkg.getPackageName();
- if (lastPkg != null) {
- out.startTag(null, "pkg");
- out.attribute(null, "n", lastPkg);
- }
- }
- out.startTag(null, "uid");
- out.attributeInt(null, "n", pkg.getUid());
- List<AppOpsManager.OpEntry> ops = pkg.getOps();
- for (int j=0; j<ops.size(); j++) {
- AppOpsManager.OpEntry op = ops.get(j);
- out.startTag(null, "op");
- out.attributeInt(null, "n", op.getOp());
- if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
- out.attributeInt(null, "m", op.getMode());
- }
-
- for (String attributionTag : op.getAttributedOpEntries().keySet()) {
- final AttributedOpEntry attribution =
- op.getAttributedOpEntries().get(attributionTag);
-
- final ArraySet<Long> keys = attribution.collectKeys();
-
- final int keyCount = keys.size();
- for (int k = 0; k < keyCount; k++) {
- final long key = keys.valueAt(k);
-
- final int uidState = AppOpsManager.extractUidStateFromKey(key);
- final int flags = AppOpsManager.extractFlagsFromKey(key);
-
- final long accessTime = attribution.getLastAccessTime(uidState,
- uidState, flags);
- final long rejectTime = attribution.getLastRejectTime(uidState,
- uidState, flags);
- final long accessDuration = attribution.getLastDuration(
- uidState, uidState, flags);
- // Proxy information for rejections is not backed up
- final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
- uidState, uidState, flags);
-
- if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
- && proxy == null) {
- continue;
- }
-
- String proxyPkg = null;
- String proxyAttributionTag = null;
- int proxyUid = Process.INVALID_UID;
- if (proxy != null) {
- proxyPkg = proxy.getPackageName();
- proxyAttributionTag = proxy.getAttributionTag();
- proxyUid = proxy.getUid();
- }
-
- out.startTag(null, "st");
- if (attributionTag != null) {
- out.attribute(null, "id", attributionTag);
- }
- out.attributeLong(null, "n", key);
- if (accessTime > 0) {
- out.attributeLong(null, "t", accessTime);
- }
- if (rejectTime > 0) {
- out.attributeLong(null, "r", rejectTime);
- }
- if (accessDuration > 0) {
- out.attributeLong(null, "d", accessDuration);
- }
- if (proxyPkg != null) {
- out.attribute(null, "pp", proxyPkg);
- }
- if (proxyAttributionTag != null) {
- out.attribute(null, "pc", proxyAttributionTag);
- }
- if (proxyUid >= 0) {
- out.attributeInt(null, "pu", proxyUid);
- }
- out.endTag(null, "st");
- }
- }
-
- out.endTag(null, "op");
- }
- out.endTag(null, "uid");
- }
- if (lastPkg != null) {
- out.endTag(null, "pkg");
- }
- }
-
- out.endTag(null, "app-ops");
- out.endDocument();
- mFile.finishWrite(stream);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write state, restoring backup.", e);
- mFile.failWrite(stream);
- }
- }
- mHistoricalRegistry.writeAndClearDiscreteHistory();
- }
-
static class Shell extends ShellCommand {
final IAppOpsService mInterface;
final AppOpsService mInternal;
@@ -4309,7 +1178,6 @@
int mode;
int packageUid;
int nonpackageUid;
- final static Binder sBinder = new Binder();
IBinder mToken;
boolean targetsUid;
@@ -4330,7 +1198,7 @@
dumpCommandHelp(pw);
}
- static private int strOpToOp(String op, PrintWriter err) {
+ static int strOpToOp(String op, PrintWriter err) {
try {
return AppOpsManager.strOpToOp(op);
} catch (IllegalArgumentException e) {
@@ -4527,6 +1395,24 @@
pw.println(" not specified, the current user is assumed.");
}
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mAppOpsService.dump(fd, pw, args);
+
+ pw.println();
+ if (mCheckOpsDelegateDispatcher.mPolicy != null
+ && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
+ AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
+ policy.dumpTags(pw);
+ } else {
+ pw.println(" AppOps policy not set.");
+ }
+
+ if (mAudioRestrictionManager.hasActiveRestrictions()) {
+ pw.println();
+ mAudioRestrictionManager.dump(pw);
+ }
+ }
static int onShellCommand(Shell shell, String cmd) {
if (cmd == null) {
return shell.handleDefaultCommands(cmd);
@@ -4730,14 +1616,12 @@
return 0;
}
case "write-settings": {
- shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+ shell.mInternal.mAppOpsService
+ .enforceManageAppOpsModes(Binder.getCallingPid(),
Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
- synchronized (shell.mInternal) {
- shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
- }
- shell.mInternal.writeState();
+ shell.mInternal.mAppOpsService.writeState();
pw.println("Current settings written.");
} finally {
Binder.restoreCallingIdentity(token);
@@ -4745,11 +1629,12 @@
return 0;
}
case "read-settings": {
- shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
- Binder.getCallingUid(), -1);
+ shell.mInternal.mAppOpsService
+ .enforceManageAppOpsModes(Binder.getCallingPid(),
+ Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
- shell.mInternal.readState();
+ shell.mInternal.mAppOpsService.readState();
pw.println("Last settings read.");
} finally {
Binder.restoreCallingIdentity(token);
@@ -4795,877 +1680,70 @@
return -1;
}
- private void dumpHelp(PrintWriter pw) {
- pw.println("AppOps service (appops) dump options:");
- pw.println(" -h");
- pw.println(" Print this help text.");
- pw.println(" --op [OP]");
- pw.println(" Limit output to data associated with the given app op code.");
- pw.println(" --mode [MODE]");
- pw.println(" Limit output to data associated with the given app op mode.");
- pw.println(" --package [PACKAGE]");
- pw.println(" Limit output to data associated with the given package name.");
- pw.println(" --attributionTag [attributionTag]");
- pw.println(" Limit output to data associated with the given attribution tag.");
- pw.println(" --include-discrete [n]");
- pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit.");
- pw.println(" --watchers");
- pw.println(" Only output the watcher sections.");
- pw.println(" --history");
- pw.println(" Only output history.");
- pw.println(" --uid-state-changes");
- pw.println(" Include logs about uid state changes.");
- }
-
- private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
- @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
- @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
- final int numAttributions = op.mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
- op.mAttributions.keyAt(i), filterAttributionTag)) {
- continue;
- }
-
- pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
- dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
- prefix + " ");
- pw.print(prefix + "]\n");
- }
- }
-
- private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
- @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
- @NonNull Date date, @NonNull String prefix) {
-
- final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
- attributionTag).getAttributedOpEntries().get(attributionTag);
-
- final ArraySet<Long> keys = entry.collectKeys();
-
- final int keyCount = keys.size();
- for (int k = 0; k < keyCount; k++) {
- final long key = keys.valueAt(k);
-
- final int uidState = AppOpsManager.extractUidStateFromKey(key);
- final int flags = AppOpsManager.extractFlagsFromKey(key);
-
- final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
- final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
- final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
- final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
-
- String proxyPkg = null;
- String proxyAttributionTag = null;
- int proxyUid = Process.INVALID_UID;
- if (proxy != null) {
- proxyPkg = proxy.getPackageName();
- proxyAttributionTag = proxy.getAttributionTag();
- proxyUid = proxy.getUid();
- }
-
- if (accessTime > 0) {
- pw.print(prefix);
- pw.print("Access: ");
- pw.print(AppOpsManager.keyToString(key));
- pw.print(" ");
- date.setTime(accessTime);
- pw.print(sdf.format(date));
- pw.print(" (");
- TimeUtils.formatDuration(accessTime - now, pw);
- pw.print(")");
- if (accessDuration > 0) {
- pw.print(" duration=");
- TimeUtils.formatDuration(accessDuration, pw);
- }
- if (proxyUid >= 0) {
- pw.print(" proxy[");
- pw.print("uid=");
- pw.print(proxyUid);
- pw.print(", pkg=");
- pw.print(proxyPkg);
- pw.print(", attributionTag=");
- pw.print(proxyAttributionTag);
- pw.print("]");
- }
- pw.println();
- }
-
- if (rejectTime > 0) {
- pw.print(prefix);
- pw.print("Reject: ");
- pw.print(AppOpsManager.keyToString(key));
- date.setTime(rejectTime);
- pw.print(sdf.format(date));
- pw.print(" (");
- TimeUtils.formatDuration(rejectTime - now, pw);
- pw.print(")");
- if (proxyUid >= 0) {
- pw.print(" proxy[");
- pw.print("uid=");
- pw.print(proxyUid);
- pw.print(", pkg=");
- pw.print(proxyPkg);
- pw.print(", attributionTag=");
- pw.print(proxyAttributionTag);
- pw.print("]");
- }
- pw.println();
- }
- }
-
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
- if (attributedOp.isRunning()) {
- long earliestElapsedTime = Long.MAX_VALUE;
- long maxNumStarts = 0;
- int numInProgressEvents = attributedOp.mInProgressEvents.size();
- for (int i = 0; i < numInProgressEvents; i++) {
- AttributedOp.InProgressStartOpEvent event =
- attributedOp.mInProgressEvents.valueAt(i);
-
- earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
- maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
- }
-
- pw.print(prefix + "Running start at: ");
- TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
- pw.println();
-
- if (maxNumStarts > 1) {
- pw.print(prefix + "startNesting=");
- pw.println(maxNumStarts);
- }
- }
- }
-
- @NeverCompile // Avoid size overhead of debugging code.
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
-
- int dumpOp = OP_NONE;
- String dumpPackage = null;
- String dumpAttributionTag = null;
- int dumpUid = Process.INVALID_UID;
- int dumpMode = -1;
- boolean dumpWatchers = false;
- // TODO ntmyren: Remove the dumpHistory and dumpFilter
- boolean dumpHistory = false;
- boolean includeDiscreteOps = false;
- boolean dumpUidStateChangeLogs = false;
- int nDiscreteOps = 10;
- @HistoricalOpsRequestFilter int dumpFilter = 0;
- boolean dumpAll = false;
-
- if (args != null) {
- for (int i = 0; i < args.length; i++) {
- String arg = args[i];
- if ("-h".equals(arg)) {
- dumpHelp(pw);
- return;
- } else if ("-a".equals(arg)) {
- // dump all data
- dumpAll = true;
- } else if ("--op".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --op option");
- return;
- }
- dumpOp = Shell.strOpToOp(args[i], pw);
- dumpFilter |= FILTER_BY_OP_NAMES;
- if (dumpOp < 0) {
- return;
- }
- } else if ("--package".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --package option");
- return;
- }
- dumpPackage = args[i];
- dumpFilter |= FILTER_BY_PACKAGE_NAME;
- try {
- dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
- PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
- 0);
- } catch (RemoteException e) {
- }
- if (dumpUid < 0) {
- pw.println("Unknown package: " + dumpPackage);
- return;
- }
- dumpUid = UserHandle.getAppId(dumpUid);
- dumpFilter |= FILTER_BY_UID;
- } else if ("--attributionTag".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --attributionTag option");
- return;
- }
- dumpAttributionTag = args[i];
- dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
- } else if ("--mode".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --mode option");
- return;
- }
- dumpMode = Shell.strModeToMode(args[i], pw);
- if (dumpMode < 0) {
- return;
- }
- } else if ("--watchers".equals(arg)) {
- dumpWatchers = true;
- } else if ("--include-discrete".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --include-discrete option");
- return;
- }
- try {
- nDiscreteOps = Integer.valueOf(args[i]);
- } catch (NumberFormatException e) {
- pw.println("Wrong parameter: " + args[i]);
- return;
- }
- includeDiscreteOps = true;
- } else if ("--history".equals(arg)) {
- dumpHistory = true;
- } else if (arg.length() > 0 && arg.charAt(0) == '-') {
- pw.println("Unknown option: " + arg);
- return;
- } else if ("--uid-state-changes".equals(arg)) {
- dumpUidStateChangeLogs = true;
- } else {
- pw.println("Unknown command: " + arg);
- return;
- }
- }
- }
-
- final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- final Date date = new Date();
- synchronized (this) {
- pw.println("Current AppOps Service state:");
- if (!dumpHistory && !dumpWatchers) {
- mConstants.dump(pw);
- }
- pw.println();
- final long now = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long nowUptime = SystemClock.uptimeMillis();
- boolean needSep = false;
- if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
- && !dumpHistory) {
- pw.println(" Profile owners:");
- for (int poi = 0; poi < mProfileOwners.size(); poi++) {
- pw.print(" User #");
- pw.print(mProfileOwners.keyAt(poi));
- pw.print(": ");
- UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
- pw.println();
- }
- pw.println();
- }
-
- if (!dumpHistory) {
- needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
- }
-
- if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
- boolean printedHeader = false;
- for (int i = 0; i < mModeWatchers.size(); i++) {
- final ModeCallback cb = mModeWatchers.valueAt(i);
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
- continue;
- }
- needSep = true;
- if (!printedHeader) {
- pw.println(" All op mode watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
- pw.print(": "); pw.println(cb);
- }
- }
- if (mActiveWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
- for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
- final SparseArray<ActiveCallback> activeWatchers =
- mActiveWatchers.valueAt(watcherNum);
- if (activeWatchers.size() <= 0) {
- continue;
- }
- final ActiveCallback cb = activeWatchers.valueAt(0);
- if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
- if (!printedHeader) {
- pw.println(" All op active watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mActiveWatchers.keyAt(watcherNum))));
- pw.println(" ->");
- pw.print(" [");
- final int opCount = activeWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
- pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mStartedWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
-
- final int watchersSize = mStartedWatchers.size();
- for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
- final SparseArray<StartedCallback> startedWatchers =
- mStartedWatchers.valueAt(watcherNum);
- if (startedWatchers.size() <= 0) {
- continue;
- }
-
- final StartedCallback cb = startedWatchers.valueAt(0);
- if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
-
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
-
- if (!printedHeader) {
- pw.println(" All op started watchers:");
- printedHeader = true;
- }
-
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mStartedWatchers.keyAt(watcherNum))));
- pw.println(" ->");
-
- pw.print(" [");
- final int opCount = startedWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
-
- pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
-
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mNotedWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
- for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
- final SparseArray<NotedCallback> notedWatchers =
- mNotedWatchers.valueAt(watcherNum);
- if (notedWatchers.size() <= 0) {
- continue;
- }
- final NotedCallback cb = notedWatchers.valueAt(0);
- if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
- if (!printedHeader) {
- pw.println(" All op noted watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mNotedWatchers.keyAt(watcherNum))));
- pw.println(" ->");
- pw.print(" [");
- final int opCount = notedWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
- pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
- && dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
- needSep = mAudioRestrictionManager.dump(pw) || needSep;
- }
- if (needSep) {
- pw.println();
- }
- for (int i=0; i<mUidStates.size(); i++) {
- UidState uidState = mUidStates.valueAt(i);
- final SparseIntArray opModes = uidState.getNonDefaultUidModes();
- final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-
- if (dumpWatchers || dumpHistory) {
- continue;
- }
- if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
- boolean hasOp = dumpOp < 0 || (opModes != null
- && opModes.indexOfKey(dumpOp) >= 0);
- boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
- boolean hasMode = dumpMode < 0;
- if (!hasMode && opModes != null) {
- for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
- if (opModes.valueAt(opi) == dumpMode) {
- hasMode = true;
- }
- }
- }
- if (pkgOps != null) {
- for (int pkgi = 0;
- (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
- pkgi++) {
- Ops ops = pkgOps.valueAt(pkgi);
- if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
- hasOp = true;
- }
- if (!hasMode) {
- for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
- if (ops.valueAt(opi).getMode() == dumpMode) {
- hasMode = true;
- }
- }
- }
- if (!hasPackage && dumpPackage.equals(ops.packageName)) {
- hasPackage = true;
- }
- }
- }
- if (uidState.foregroundOps != null && !hasOp) {
- if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
- hasOp = true;
- }
- }
- if (!hasOp || !hasPackage || !hasMode) {
- continue;
- }
- }
-
- pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
- uidState.dump(pw, nowElapsed);
- if (uidState.foregroundOps != null && (dumpMode < 0
- || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
- pw.println(" foregroundOps:");
- for (int j = 0; j < uidState.foregroundOps.size(); j++) {
- if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
- continue;
- }
- pw.print(" ");
- pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
- pw.print(": ");
- pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
- }
- pw.print(" hasForegroundWatchers=");
- pw.println(uidState.hasForegroundWatchers);
- }
- needSep = true;
-
- if (opModes != null) {
- final int opModeCount = opModes.size();
- for (int j = 0; j < opModeCount; j++) {
- final int code = opModes.keyAt(j);
- final int mode = opModes.valueAt(j);
- if (dumpOp >= 0 && dumpOp != code) {
- continue;
- }
- if (dumpMode >= 0 && dumpMode != mode) {
- continue;
- }
- pw.print(" "); pw.print(AppOpsManager.opToName(code));
- pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
- }
- }
-
- if (pkgOps == null) {
- continue;
- }
-
- for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
- final Ops ops = pkgOps.valueAt(pkgi);
- if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
- continue;
- }
- boolean printedPackage = false;
- for (int j=0; j<ops.size(); j++) {
- final Op op = ops.valueAt(j);
- final int opCode = op.op;
- if (dumpOp >= 0 && dumpOp != opCode) {
- continue;
- }
- if (dumpMode >= 0 && dumpMode != op.getMode()) {
- continue;
- }
- if (!printedPackage) {
- pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
- printedPackage = true;
- }
- pw.print(" "); pw.print(AppOpsManager.opToName(opCode));
- pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
- final int switchOp = AppOpsManager.opToSwitch(opCode);
- if (switchOp != opCode) {
- pw.print(" / switch ");
- pw.print(AppOpsManager.opToName(switchOp));
- final Op switchObj = ops.get(switchOp);
- int mode = switchObj == null
- ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
- pw.print("="); pw.print(AppOpsManager.modeToName(mode));
- }
- pw.println("): ");
- dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
- sdf, date, " ");
- }
- }
- }
- if (needSep) {
- pw.println();
- }
-
- boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
- mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
-
- if (!dumpHistory && !dumpWatchers) {
- pw.println();
- if (mCheckOpsDelegateDispatcher.mPolicy != null
- && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
- AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
- policy.dumpTags(pw);
- } else {
- pw.println(" AppOps policy not set.");
- }
- }
-
- if (dumpAll || dumpUidStateChangeLogs) {
- pw.println();
- pw.println("Uid State Changes Event Log:");
- getUidStateTracker().dumpEvents(pw);
- }
- }
-
- // Must not hold the appops lock
- if (dumpHistory && !dumpWatchers) {
- mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
- dumpFilter);
- }
- if (includeDiscreteOps) {
- pw.println("Discrete accesses: ");
- mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
- dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
- }
- }
-
@Override
public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
- checkSystemUid("setUserRestrictions");
- Objects.requireNonNull(restrictions);
- Objects.requireNonNull(token);
- for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
- String restriction = AppOpsManager.opToRestriction(i);
- if (restriction != null) {
- setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
- userHandle, null);
- }
- }
+ mAppOpsService.setUserRestrictions(restrictions, token, userHandle);
}
@Override
public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
PackageTagsList excludedPackageTags) {
- if (Binder.getCallingPid() != Process.myPid()) {
- mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
- if (userHandle != UserHandle.getCallingUserId()) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission
- .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
- && mContext.checkCallingOrSelfPermission(Manifest.permission
- .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
- + " INTERACT_ACROSS_USERS to interact cross user ");
- }
- }
- verifyIncomingOp(code);
- Objects.requireNonNull(token);
- setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
- }
-
- private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
- int userHandle, PackageTagsList excludedPackageTags) {
- synchronized (AppOpsService.this) {
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
-
- if (restrictionState == null) {
- try {
- restrictionState = new ClientUserRestrictionState(token);
- } catch (RemoteException e) {
- return;
- }
- mOpUserRestrictions.put(token, restrictionState);
- }
-
- if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
- userHandle)) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, this, code, restricted,
- userHandle));
- }
-
- if (restrictionState.isDefault()) {
- mOpUserRestrictions.remove(token);
- restrictionState.destroy();
- }
- }
- }
-
- private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
- synchronized (AppOpsService.this) {
- int numUids = mUidStates.size();
- for (int uidNum = 0; uidNum < numUids; uidNum++) {
- int uid = mUidStates.keyAt(uidNum);
- if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
- continue;
- }
- updateStartedOpModeForUidLocked(code, restricted, uid);
- }
- }
- }
-
- private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
-
- int numPkgOps = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
- Op op = ops != null ? ops.get(code) : null;
- if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
- continue;
- }
- int numAttrTags = op.mAttributions.size();
- for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
- AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
- if (restricted && attrOp.isRunning()) {
- attrOp.pause();
- } else if (attrOp.isPaused()) {
- attrOp.resume();
- }
- }
- }
- }
-
- private void notifyWatchersOfChange(int code, int uid) {
- final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
- synchronized (this) {
- modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (modeChangedListenerSet == null) {
- return;
- }
- }
-
- notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ mAppOpsService.setUserRestriction(code, restricted, token, userHandle,
+ excludedPackageTags);
}
@Override
public void removeUser(int userHandle) throws RemoteException {
- checkSystemUid("removeUser");
- synchronized (AppOpsService.this) {
- final int tokenCount = mOpUserRestrictions.size();
- for (int i = tokenCount - 1; i >= 0; i--) {
- ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
- opRestrictions.removeUser(userHandle);
- }
- removeUidsForUserLocked(userHandle);
- }
+ mAppOpsService.removeUser(userHandle);
}
@Override
public boolean isOperationActive(int code, int uid, String packageName) {
- if (Binder.getCallingUid() != uid) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- }
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return false;
- }
-
- final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return false;
- }
- // TODO moltmann: Allow to check for attribution op activeness
- synchronized (AppOpsService.this) {
- Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
- if (pkgOps == null) {
- return false;
- }
-
- Op op = pkgOps.get(code);
- if (op == null) {
- return false;
- }
-
- return op.isRunning();
- }
+ return mAppOpsService.isOperationActive(code, uid, packageName);
}
@Override
public boolean isProxying(int op, @NonNull String proxyPackageName,
@NonNull String proxyAttributionTag, int proxiedUid,
@NonNull String proxiedPackageName) {
- Objects.requireNonNull(proxyPackageName);
- Objects.requireNonNull(proxiedPackageName);
- final long callingUid = Binder.getCallingUid();
- final long identity = Binder.clearCallingIdentity();
- try {
- final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
- proxiedPackageName, new int[] {op});
- if (packageOps == null || packageOps.isEmpty()) {
- return false;
- }
- final List<OpEntry> opEntries = packageOps.get(0).getOps();
- if (opEntries.isEmpty()) {
- return false;
- }
- final OpEntry opEntry = opEntries.get(0);
- if (!opEntry.isRunning()) {
- return false;
- }
- final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
- OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
- return proxyInfo != null && callingUid == proxyInfo.getUid()
- && proxyPackageName.equals(proxyInfo.getPackageName())
- && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag,
+ proxiedUid, proxiedPackageName);
}
@Override
public void resetPackageOpsNoHistory(@NonNull String packageName) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "resetPackageOpsNoHistory");
- synchronized (AppOpsService.this) {
- final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
- UserHandle.getCallingUserId());
- if (uid == Process.INVALID_UID) {
- return;
- }
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
- Ops removedOps = uidState.pkgOps.remove(packageName);
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- if (removedOps != null) {
- scheduleFastWriteLocked();
- }
- }
+ mAppOpsService.resetPackageOpsNoHistory(packageName);
}
@Override
public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
long baseSnapshotInterval, int compressionStep) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "setHistoryParameters");
- // Must not hold the appops lock
- mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
}
@Override
public void offsetHistory(long offsetMillis) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "offsetHistory");
- // Must not hold the appops lock
- mHistoricalRegistry.offsetHistory(offsetMillis);
- mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+ mAppOpsService.offsetHistory(offsetMillis);
}
@Override
public void addHistoricalOps(HistoricalOps ops) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "addHistoricalOps");
- // Must not hold the appops lock
- mHistoricalRegistry.addHistoricalOps(ops);
+ mAppOpsService.addHistoricalOps(ops);
}
@Override
public void resetHistoryParameters() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "resetHistoryParameters");
- // Must not hold the appops lock
- mHistoricalRegistry.resetHistoryParameters();
+ mAppOpsService.resetHistoryParameters();
}
@Override
public void clearHistory() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "clearHistory");
- // Must not hold the appops lock
- mHistoricalRegistry.clearAllHistory();
+ mAppOpsService.clearHistory();
}
@Override
public void rebootHistory(long offlineDurationMillis) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "rebootHistory");
-
- Preconditions.checkArgument(offlineDurationMillis >= 0);
-
- // Must not hold the appops lock
- mHistoricalRegistry.shutdown();
-
- if (offlineDurationMillis > 0) {
- SystemClock.sleep(offlineDurationMillis);
- }
-
- mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
- mHistoricalRegistry.systemReady(mContext.getContentResolver());
- mHistoricalRegistry.persistPendingHistory();
+ mAppOpsService.rebootHistory(offlineDurationMillis);
}
/**
@@ -5920,24 +1998,6 @@
return false;
}
- @GuardedBy("this")
- private void removeUidsForUserLocked(int userHandle) {
- for (int i = mUidStates.size() - 1; i >= 0; --i) {
- final int uid = mUidStates.keyAt(i);
- if (UserHandle.getUserId(uid) == userHandle) {
- mUidStates.valueAt(i).clear();
- mUidStates.removeAt(i);
- }
- }
- }
-
- private void checkSystemUid(String function) {
- int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID) {
- throw new SecurityException(function + " must by called by the system");
- }
- }
-
private static int resolveUid(String packageName) {
if (packageName == null) {
return Process.INVALID_UID;
@@ -5958,184 +2018,43 @@
return Process.INVALID_UID;
}
- private static String[] getPackagesForUid(int uid) {
- String[] packageNames = null;
-
- // Very early during boot the package manager is not yet or not yet fully started. At this
- // time there are no packages yet.
- if (AppGlobals.getPackageManager() != null) {
- try {
- packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
- } catch (RemoteException e) {
- /* ignore - local call */
- }
- }
- if (packageNames == null) {
- return EmptyArray.STRING;
- }
- return packageNames;
- }
-
- private final class ClientUserRestrictionState implements DeathRecipient {
- private final IBinder token;
-
- ClientUserRestrictionState(IBinder token)
- throws RemoteException {
- token.linkToDeath(this, 0);
- this.token = token;
- }
-
- public boolean setRestriction(int code, boolean restricted,
- PackageTagsList excludedPackageTags, int userId) {
- return mAppOpsRestrictions.setUserRestriction(token, userId, code,
- restricted, excludedPackageTags);
- }
-
- public boolean hasRestriction(int code, String packageName, String attributionTag,
- int userId, boolean isCheckOp) {
- return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
- attributionTag, isCheckOp);
- }
-
- public void removeUser(int userId) {
- mAppOpsRestrictions.clearUserRestrictions(token, userId);
- }
-
- public boolean isDefault() {
- return !mAppOpsRestrictions.hasUserRestrictions(token);
- }
-
- @Override
- public void binderDied() {
- synchronized (AppOpsService.this) {
- mAppOpsRestrictions.clearUserRestrictions(token);
- mOpUserRestrictions.remove(token);
- destroy();
- }
- }
-
- public void destroy() {
- token.unlinkToDeath(this, 0);
- }
- }
-
- private final class ClientGlobalRestrictionState implements DeathRecipient {
- final IBinder mToken;
-
- ClientGlobalRestrictionState(IBinder token)
- throws RemoteException {
- token.linkToDeath(this, 0);
- this.mToken = token;
- }
-
- boolean setRestriction(int code, boolean restricted) {
- return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
- }
-
- boolean hasRestriction(int code) {
- return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
- }
-
- boolean isDefault() {
- return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
- }
-
- @Override
- public void binderDied() {
- mAppOpsRestrictions.clearGlobalRestrictions(mToken);
- mOpGlobalRestrictions.remove(mToken);
- destroy();
- }
-
- void destroy() {
- mToken.unlinkToDeath(this, 0);
- }
- }
-
private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
@Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
- synchronized (AppOpsService.this) {
- mProfileOwners = owners;
- }
+ AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners);
}
@Override
public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
boolean visible) {
- AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
+ AppOpsService.this.mAppOpsService
+ .updateAppWidgetVisibility(uidPackageNames, visible);
}
@Override
public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
@Nullable IAppOpsCallback callback) {
- setUidMode(code, uid, mode, callback);
+ AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback);
}
@Override
public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
int mode, @Nullable IAppOpsCallback callback) {
- setMode(code, uid, packageName, mode, callback);
+ AppOpsService.this.mAppOpsService
+ .setMode(code, uid, packageName, mode, callback);
}
@Override
public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
- if (Binder.getCallingPid() != Process.myPid()) {
- // TODO instead of this enforcement put in AppOpsManagerInternal
- throw new SecurityException("Only the system can set global restrictions");
- }
-
- synchronized (AppOpsService.this) {
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
-
- if (restrictionState == null) {
- try {
- restrictionState = new ClientGlobalRestrictionState(token);
- } catch (RemoteException e) {
- return;
- }
- mOpGlobalRestrictions.put(token, restrictionState);
- }
-
- if (restrictionState.setRestriction(code, restricted)) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
- UID_ANY));
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
- code, restricted, UserHandle.USER_ALL));
- }
-
- if (restrictionState.isDefault()) {
- mOpGlobalRestrictions.remove(token);
- restrictionState.destroy();
- }
- }
+ AppOpsService.this.mAppOpsService
+ .setGlobalRestriction(code, restricted, token);
}
@Override
public int getOpRestrictionCount(int code, UserHandle user, String pkg,
String attributionTag) {
- int number = 0;
- synchronized (AppOpsService.this) {
- int numRestrictions = mOpUserRestrictions.size();
- for (int i = 0; i < numRestrictions; i++) {
- if (mOpUserRestrictions.valueAt(i)
- .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
- false)) {
- number++;
- }
- }
-
- numRestrictions = mOpGlobalRestrictions.size();
- for (int i = 0; i < numRestrictions; i++) {
- if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
- number++;
- }
- }
- }
-
- return number;
+ return AppOpsService.this.mAppOpsService
+ .getOpRestrictionCount(code, user, pkg, attributionTag);
}
}
@@ -6431,7 +2350,7 @@
attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
}
- public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6461,7 +2380,7 @@
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
- private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+ private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6495,7 +2414,7 @@
AppOpsService.this::finishOperationImpl);
}
- public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ public void finishProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
@@ -6513,7 +2432,7 @@
}
}
- private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+ private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
new file mode 100644
index 0000000..70f3bcc
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -0,0 +1,4679 @@
+/*
+ * Copyright (C) 2012 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.appop;
+
+import static android.app.AppOpsManager.AttributedOpEntry;
+import static android.app.AppOpsManager.AttributionFlags;
+import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
+import static android.app.AppOpsManager.HistoricalOps;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.Mode;
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_VIBRATE;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
+import static android.app.AppOpsManager.OpEntry;
+import static android.app.AppOpsManager.OpEventProxyInfo;
+import static android.app.AppOpsManager.OpFlags;
+import static android.app.AppOpsManager.RestrictionBypass;
+import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
+import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToName;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
+
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.PackageTagsList;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.Clock;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import libcore.util.EmptyArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+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.Map;
+import java.util.Objects;
+import java.util.Set;
+
+class AppOpsServiceImpl implements AppOpsServiceInterface {
+ static final String TAG = "AppOps";
+ static final boolean DEBUG = false;
+
+ private static final int NO_VERSION = -1;
+ /**
+ * Increment by one every time and add the corresponding upgrade logic in
+ * {@link #upgradeLocked(int)} below. The first version was 1
+ */
+ private static final int CURRENT_VERSION = 1;
+
+ // Write at most every 30 minutes.
+ static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
+
+ // Constant meaning that any UID should be matched when dispatching callbacks
+ private static final int UID_ANY = -2;
+
+ private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+ OP_PLAY_AUDIO,
+ OP_RECORD_AUDIO,
+ OP_CAMERA,
+ OP_VIBRATE,
+ };
+ private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
+ final Context mContext;
+ final AtomicFile mFile;
+ final Handler mHandler;
+
+ /**
+ * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+ * objects
+ */
+ @GuardedBy("this")
+ final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+ new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+ /**
+ * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+ * new objects
+ */
+ @GuardedBy("this")
+ final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+ new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+ MAX_UNUSED_POOLED_OBJECTS);
+ @Nullable
+ private final DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+ boolean mWriteScheduled;
+ boolean mFastWriteScheduled;
+ final Runnable mWriteRunner = new Runnable() {
+ public void run() {
+ synchronized (AppOpsServiceImpl.this) {
+ mWriteScheduled = false;
+ mFastWriteScheduled = false;
+ AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ writeState();
+ return null;
+ }
+ };
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+ }
+ };
+
+ @GuardedBy("this")
+ @VisibleForTesting
+ final SparseArray<UidState> mUidStates = new SparseArray<>();
+
+ volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
+ /*
+ * These are app op restrictions imposed per user from various parties.
+ */
+ private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
+ new ArrayMap<>();
+
+ /*
+ * These are app op restrictions imposed globally from various parties within the system.
+ */
+ private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
+ new ArrayMap<>();
+
+ SparseIntArray mProfileOwners;
+
+ /**
+ * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+ * changed
+ */
+ private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
+
+ /**
+ * Package Manager internal. Access via {@link #getPackageManagerInternal()}
+ */
+ private @Nullable PackageManagerInternal mPackageManagerInternal;
+
+ /**
+ * Interface for app-op modes.
+ */
+ @VisibleForTesting
+ AppOpsCheckingServiceInterface mAppOpsServiceInterface;
+
+ /**
+ * Interface for app-op restrictions.
+ */
+ @VisibleForTesting
+ AppOpsRestrictions mAppOpsRestrictions;
+
+ private AppOpsUidStateTracker mUidStateTracker;
+
+ /**
+ * Hands the definition of foreground and uid states
+ */
+ @GuardedBy("this")
+ public AppOpsUidStateTracker getUidStateTracker() {
+ if (mUidStateTracker == null) {
+ mUidStateTracker = new AppOpsUidStateTrackerImpl(
+ LocalServices.getService(ActivityManagerInternal.class),
+ mHandler,
+ r -> {
+ synchronized (AppOpsServiceImpl.this) {
+ r.run();
+ }
+ },
+ Clock.SYSTEM_CLOCK, mConstants);
+
+ mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+ this::onUidStateChanged);
+ }
+ return mUidStateTracker;
+ }
+
+ /**
+ * All times are in milliseconds. These constants are kept synchronized with the system
+ * global Settings. Any access to this class or its fields should be done while
+ * holding the AppOpsService lock.
+ */
+ final class Constants extends ContentObserver {
+
+ /**
+ * How long we want for a drop in uid state from top to settle before applying it.
+ *
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
+ */
+ public long TOP_STATE_SETTLE_TIME;
+
+ /**
+ * How long we want for a drop in uid state from foreground to settle before applying it.
+ *
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
+ */
+ public long FG_SERVICE_STATE_SETTLE_TIME;
+
+ /**
+ * How long we want for a drop in uid state from background to settle before applying it.
+ *
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
+ */
+ public long BG_STATE_SETTLE_TIME;
+
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+ private ContentResolver mResolver;
+
+ Constants(Handler handler) {
+ super(handler);
+ updateConstants();
+ }
+
+ public void startMonitoring(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
+ false, this);
+ updateConstants();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ String value = mResolver != null ? Settings.Global.getString(mResolver,
+ Settings.Global.APP_OPS_CONSTANTS) : "";
+
+ synchronized (AppOpsServiceImpl.this) {
+ try {
+ mParser.setString(value);
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e(TAG, "Bad app ops settings", e);
+ }
+ TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
+ FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
+ BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(" Settings:");
+
+ pw.print(" ");
+ pw.print(KEY_TOP_STATE_SETTLE_TIME);
+ pw.print("=");
+ TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
+ pw.println();
+ pw.print(" ");
+ pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME);
+ pw.print("=");
+ TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
+ pw.println();
+ pw.print(" ");
+ pw.print(KEY_BG_STATE_SETTLE_TIME);
+ pw.print("=");
+ TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
+ pw.println();
+ }
+ }
+
+ @VisibleForTesting
+ final Constants mConstants;
+
+ @VisibleForTesting
+ final class UidState {
+ public final int uid;
+
+ public ArrayMap<String, Ops> pkgOps;
+
+ // true indicates there is an interested observer, false there isn't but it has such an op
+ //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
+ public SparseBooleanArray foregroundOps;
+ public boolean hasForegroundWatchers;
+
+ public UidState(int uid) {
+ this.uid = uid;
+ }
+
+ public void clear() {
+ mAppOpsServiceInterface.removeUid(uid);
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+ }
+ pkgOps = null;
+ }
+
+ public boolean isDefault() {
+ boolean areAllPackageModesDefault = true;
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
+ UserHandle.getUserId(uid))) {
+ areAllPackageModesDefault = false;
+ break;
+ }
+ }
+ }
+ return (pkgOps == null || pkgOps.isEmpty())
+ && mAppOpsServiceInterface.areUidModesDefault(uid)
+ && areAllPackageModesDefault;
+ }
+
+ // Functions for uid mode access and manipulation.
+ public SparseIntArray getNonDefaultUidModes() {
+ return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
+ }
+
+ public int getUidMode(int op) {
+ return mAppOpsServiceInterface.getUidMode(uid, op);
+ }
+
+ public boolean setUidMode(int op, int mode) {
+ return mAppOpsServiceInterface.setUidMode(uid, op, mode);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ int evalMode(int op, int mode) {
+ return getUidStateTracker().evalMode(uid, op, mode);
+ }
+
+ public void evalForegroundOps() {
+ foregroundOps = null;
+ foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
+ if (pkgOps != null) {
+ for (int i = pkgOps.size() - 1; i >= 0; i--) {
+ foregroundOps = mAppOpsServiceInterface
+ .evalForegroundPackageOps(pkgOps.valueAt(i).packageName,
+ foregroundOps,
+ UserHandle.getUserId(uid));
+ }
+ }
+ hasForegroundWatchers = false;
+ if (foregroundOps != null) {
+ for (int i = 0; i < foregroundOps.size(); i++) {
+ if (foregroundOps.valueAt(i)) {
+ hasForegroundWatchers = true;
+ break;
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("GuardedBy")
+ public int getState() {
+ return getUidStateTracker().getUidState(uid);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ public void dump(PrintWriter pw, long nowElapsed) {
+ getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
+ }
+ }
+
+ static final class Ops extends SparseArray<Op> {
+ final String packageName;
+ final UidState uidState;
+
+ /**
+ * The restriction properties of the package. If {@code null} it could not have been read
+ * yet and has to be refreshed.
+ */
+ @Nullable RestrictionBypass bypass;
+
+ /** Lazily populated cache of attributionTags of this package */
+ final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
+
+ /**
+ * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
+ * than or equal to {@link #knownAttributionTags}.
+ */
+ final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
+
+ Ops(String _packageName, UidState _uidState) {
+ packageName = _packageName;
+ uidState = _uidState;
+ }
+ }
+
+ /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
+ private static final class PackageVerificationResult {
+
+ final RestrictionBypass bypass;
+ final boolean isAttributionTagValid;
+
+ PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
+ this.bypass = bypass;
+ this.isAttributionTagValid = isAttributionTagValid;
+ }
+ }
+
+ final class Op {
+ int op;
+ int uid;
+ final UidState uidState;
+ final @NonNull String packageName;
+
+ /** attributionTag -> AttributedOp */
+ final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+
+ Op(UidState uidState, String packageName, int op, int uid) {
+ this.op = op;
+ this.uid = uid;
+ this.uidState = uidState;
+ this.packageName = packageName;
+ }
+
+ @Mode int getMode() {
+ return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
+ UserHandle.getUserId(this.uid));
+ }
+
+ void setMode(@Mode int mode) {
+ mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
+ UserHandle.getUserId(this.uid));
+ }
+
+ void removeAttributionsWithNoTime() {
+ for (int i = mAttributions.size() - 1; i >= 0; i--) {
+ if (!mAttributions.valueAt(i).hasAnyTime()) {
+ mAttributions.removeAt(i);
+ }
+ }
+ }
+
+ private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+ @Nullable String attributionTag) {
+ AttributedOp attributedOp;
+
+ attributedOp = mAttributions.get(attributionTag);
+ if (attributedOp == null) {
+ attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag,
+ parent);
+ mAttributions.put(attributionTag, attributedOp);
+ }
+
+ return attributedOp;
+ }
+
+ @NonNull
+ OpEntry createEntryLocked() {
+ final int numAttributions = mAttributions.size();
+
+ final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
+ new ArrayMap<>(numAttributions);
+ for (int i = 0; i < numAttributions; i++) {
+ attributionEntries.put(mAttributions.keyAt(i),
+ mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ }
+
+ return new OpEntry(op, getMode(), attributionEntries);
+ }
+
+ @NonNull
+ OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
+ final int numAttributions = mAttributions.size();
+
+ final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
+ for (int i = 0; i < numAttributions; i++) {
+ if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
+ attributionEntries.put(mAttributions.keyAt(i),
+ mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ break;
+ }
+ }
+
+ return new OpEntry(op, getMode(), attributionEntries);
+ }
+
+ boolean isRunning() {
+ final int numAttributions = mAttributions.size();
+ for (int i = 0; i < numAttributions; i++) {
+ if (mAttributions.valueAt(i).isRunning()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
+
+ final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
+ /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+ public static final int ALL_OPS = -2;
+
+ // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+ // Otherwise we can just use the IBinder object.
+ private final IAppOpsCallback mCallback;
+
+ ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
+ int callingUid, int callingPid) {
+ super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+ this.mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ModeCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, getWatchingUid());
+ sb.append(" flags=0x");
+ sb.append(Integer.toHexString(getFlags()));
+ switch (getWatchedOpCode()) {
+ case OP_NONE:
+ break;
+ case ALL_OPS:
+ sb.append(" op=(all)");
+ break;
+ default:
+ sb.append(" op=");
+ sb.append(opToName(getWatchedOpCode()));
+ break;
+ }
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, getCallingUid());
+ sb.append(" pid=");
+ sb.append(getCallingPid());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void unlinkToDeath() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingMode(mCallback);
+ }
+
+ @Override
+ public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+ mCallback.opChanged(op, uid, packageName);
+ }
+ }
+
+ final class ActiveCallback implements DeathRecipient {
+ final IAppOpsActiveCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ActiveCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingActive(mCallback);
+ }
+ }
+
+ final class StartedCallback implements DeathRecipient {
+ final IAppOpsStartedCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("StartedCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingStarted(mCallback);
+ }
+ }
+
+ final class NotedCallback implements DeathRecipient {
+ final IAppOpsNotedCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("NotedCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingNoted(mCallback);
+ }
+ }
+
+ /**
+ * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
+ */
+ static void onClientDeath(@NonNull AttributedOp attributedOp,
+ @NonNull IBinder clientId) {
+ attributedOp.onClientDeath(clientId);
+ }
+
+ AppOpsServiceImpl(File storagePath, Handler handler, Context context) {
+ mContext = context;
+
+ for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+ int switchCode = AppOpsManager.opToSwitch(switchedCode);
+ mSwitchedOps.put(switchCode,
+ ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+ }
+ mAppOpsServiceInterface =
+ new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+ mAppOpsServiceInterface);
+
+ LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+ mFile = new AtomicFile(storagePath, "appops");
+
+ mHandler = handler;
+ mConstants = new Constants(mHandler);
+ readState();
+ }
+
+ /**
+ * Handler for work when packages are removed or updated
+ */
+ private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+ if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+ synchronized (AppOpsServiceImpl.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
+ Ops removedOps = uidState.pkgOps.remove(pkgName);
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
+ } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+ AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
+ if (pkg == null) {
+ return;
+ }
+
+ ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
+ ArraySet<String> attributionTags = new ArraySet<>();
+ attributionTags.add(null);
+ if (pkg.getAttributions() != null) {
+ int numAttributions = pkg.getAttributions().size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
+ attributionTags.add(attribution.getTag());
+
+ int numInheritFrom = attribution.getInheritFrom().size();
+ for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+ inheritFromNum++) {
+ dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
+ attribution.getTag());
+ }
+ }
+ }
+
+ synchronized (AppOpsServiceImpl.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+
+ Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ return;
+ }
+
+ // Reset cached package properties to re-initialize when needed
+ ops.bypass = null;
+ ops.knownAttributionTags.clear();
+
+ // Merge data collected for removed attributions into their successor
+ // attributions
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+
+ int numAttributions = op.mAttributions.size();
+ for (int attributionNum = numAttributions - 1; attributionNum >= 0;
+ attributionNum--) {
+ String attributionTag = op.mAttributions.keyAt(attributionNum);
+
+ if (attributionTags.contains(attributionTag)) {
+ // attribution still exist after upgrade
+ continue;
+ }
+
+ String newAttributionTag = dstAttributionTags.get(attributionTag);
+
+ AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+ newAttributionTag);
+ newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
+ op.mAttributions.removeAt(attributionNum);
+
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+ }
+ }
+ };
+
+ @Override
+ public void systemReady() {
+ mConstants.startMonitoring(mContext.getContentResolver());
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+ IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageUpdateFilter.addDataScheme("package");
+
+ mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+ packageUpdateFilter, null, null);
+
+ synchronized (this) {
+ for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+ int uid = mUidStates.keyAt(uidNum);
+ UidState uidState = mUidStates.valueAt(uidNum);
+
+ String[] pkgsInUid = getPackagesForUid(uidState.uid);
+ if (ArrayUtils.isEmpty(pkgsInUid)) {
+ uidState.clear();
+ mUidStates.removeAt(uidNum);
+ scheduleFastWriteLocked();
+ continue;
+ }
+
+ ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+ if (pkgs == null) {
+ continue;
+ }
+
+ int numPkgs = pkgs.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ String pkg = pkgs.keyAt(pkgNum);
+
+ String action;
+ if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+ action = Intent.ACTION_PACKAGE_REMOVED;
+ } else {
+ action = Intent.ACTION_PACKAGE_REPLACED;
+ }
+
+ SystemServerInitThreadPool.submit(
+ () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+ .setData(Uri.fromParts("package", pkg, null))
+ .putExtra(Intent.EXTRA_UID, uid)),
+ "Update app-ops uidState in case package " + pkg + " changed");
+ }
+ }
+ }
+
+ final IntentFilter packageSuspendFilter = new IntentFilter();
+ packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ final String[] changedPkgs = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+ ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+ synchronized (AppOpsServiceImpl.this) {
+ onModeChangedListeners =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (onModeChangedListeners == null) {
+ continue;
+ }
+ }
+ for (int i = 0; i < changedUids.length; i++) {
+ final int changedUid = changedUids[i];
+ final String changedPkg = changedPkgs[i];
+ // We trust packagemanager to insert matching uid and packageNames in the
+ // extras
+ notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+ }
+ }
+ }
+ }, UserHandle.ALL, packageSuspendFilter, null, null);
+ }
+
+ @Override
+ public void packageRemoved(int uid, String packageName) {
+ synchronized (this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ return;
+ }
+
+ Ops removedOps = null;
+
+ // Remove any package state if such.
+ if (uidState.pkgOps != null) {
+ removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+
+ // If we just nuked the last package state check if the UID is valid.
+ if (removedOps != null && uidState.pkgOps.isEmpty()
+ && getPackagesForUid(uid).length <= 0) {
+ uidState.clear();
+ mUidStates.remove(uid);
+ }
+
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+
+ final int numOps = removedOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = removedOps.valueAt(opNum);
+
+ final int numAttributions = op.mAttributions.size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
+
+ while (attributedOp.isRunning()) {
+ attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+ }
+ while (attributedOp.isPaused()) {
+ attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ }
+ }
+ }
+ }
+ }
+
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+ mHistoricalRegistry, uid, packageName));
+ }
+
+ @Override
+ public void uidRemoved(int uid) {
+ synchronized (this) {
+ if (mUidStates.indexOfKey(uid) >= 0) {
+ mUidStates.get(uid).clear();
+ mUidStates.remove(uid);
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+
+ // The callback method from ForegroundPolicyInterface
+ private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, true);
+
+ if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
+ for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
+ if (!uidState.foregroundOps.valueAt(fgi)) {
+ continue;
+ }
+ final int code = uidState.foregroundOps.keyAt(fgi);
+
+ if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
+ && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
+ this, code, uidState.uid, true, null));
+ } else if (uidState.pkgOps != null) {
+ final ArraySet<OnOpModeChangedListener> listenerSet =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (listenerSet != null) {
+ for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+ final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+ if ((listener.getFlags()
+ & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+ || !listener.isWatchingUid(uidState.uid)) {
+ continue;
+ }
+ for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
+ final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
+ if (op == null) {
+ continue;
+ }
+ if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChanged,
+ this, listenerSet.valueAt(cbi), code, uidState.uid,
+ uidState.pkgOps.keyAt(pkgi)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (uidState != null && uidState.pkgOps != null) {
+ int numPkgs = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+
+ int numAttributions = op.mAttributions.size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ AttributedOp attributedOp = op.mAttributions.valueAt(
+ attributionNum);
+
+ attributedOp.onUidStateChanged(state);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Notify the proc state or capability has changed for a certain UID.
+ */
+ @Override
+ public void updateUidProcState(int uid, int procState,
+ @ActivityManager.ProcessCapability int capability) {
+ synchronized (this) {
+ getUidStateTracker().updateUidProcState(uid, procState, capability);
+ if (!mUidStates.contains(uid)) {
+ UidState uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ onUidStateChanged(uid,
+ AppOpsUidStateTracker.processStateToUidState(procState), false);
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ Slog.w(TAG, "Writing app ops before shutdown...");
+ boolean doWrite = false;
+ synchronized (this) {
+ if (mWriteScheduled) {
+ mWriteScheduled = false;
+ mFastWriteScheduled = false;
+ mHandler.removeCallbacks(mWriteRunner);
+ doWrite = true;
+ }
+ }
+ if (doWrite) {
+ writeState();
+ }
+
+ mHistoricalRegistry.shutdown();
+ }
+
+ private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<>();
+ for (int j = 0; j < pkgOps.size(); j++) {
+ Op curOp = pkgOps.valueAt(j);
+ resOps.add(getOpEntryForResult(curOp));
+ }
+ } else {
+ for (int j = 0; j < ops.length; j++) {
+ Op curOp = pkgOps.get(ops[j]);
+ if (curOp != null) {
+ if (resOps == null) {
+ resOps = new ArrayList<>();
+ }
+ resOps.add(getOpEntryForResult(curOp));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ @Nullable
+ private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
+ @Nullable int[] ops) {
+ final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes == null) {
+ return null;
+ }
+
+ int opModeCount = opModes.size();
+ if (opModeCount == 0) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<>();
+ for (int i = 0; i < opModeCount; i++) {
+ int code = opModes.keyAt(i);
+ resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+ }
+ } else {
+ for (int j = 0; j < ops.length; j++) {
+ int code = ops[j];
+ if (opModes.indexOfKey(code) >= 0) {
+ if (resOps == null) {
+ resOps = new ArrayList<>();
+ }
+ resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
+ return op.createEntryLocked();
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+ final int callingUid = Binder.getCallingUid();
+ final boolean hasAllPackageAccess = mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+ Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
+ ArrayList<AppOpsManager.PackageOps> res = null;
+ synchronized (this) {
+ final int uidStateCount = mUidStates.size();
+ for (int i = 0; i < uidStateCount; i++) {
+ UidState uidState = mUidStates.valueAt(i);
+ if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+ continue;
+ }
+ ArrayMap<String, Ops> packages = uidState.pkgOps;
+ final int packageCount = packages.size();
+ for (int j = 0; j < packageCount; j++) {
+ Ops pkgOps = packages.valueAt(j);
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps != null) {
+ if (res == null) {
+ res = new ArrayList<>();
+ }
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uidState.uid, resOps);
+ // Caller can always see their packages and with a permission all.
+ if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+ res.add(resPackage);
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+ int[] ops) {
+ enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName);
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return Collections.emptyList();
+ }
+ synchronized (this) {
+ Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
+ /* edit */ false);
+ if (pkgOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uidState.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ // We get to access everything
+ if (callingUid == Process.myPid()) {
+ return;
+ }
+ // Apps can access their own data
+ if (uid == callingUid && packageName != null
+ && checkPackage(uid, packageName) == MODE_ALLOWED) {
+ return;
+ }
+ // Otherwise, you need a permission...
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), callingUid, null);
+ }
+
+ /**
+ * Verify that historical appop request arguments are valid.
+ */
+ private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
+ String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags) {
+ if ((filter & FILTER_BY_UID) != 0) {
+ Preconditions.checkArgument(uid != Process.INVALID_UID);
+ } else {
+ Preconditions.checkArgument(uid == Process.INVALID_UID);
+ }
+
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+ Objects.requireNonNull(packageName);
+ } else {
+ Preconditions.checkArgument(packageName == null);
+ }
+
+ if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
+ Preconditions.checkArgument(attributionTag == null);
+ }
+
+ if ((filter & FILTER_BY_OP_NAMES) != 0) {
+ Objects.requireNonNull(opNames);
+ } else {
+ Preconditions.checkArgument(opNames == null);
+ }
+
+ Preconditions.checkFlagsArgument(filter,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
+ | FILTER_BY_OP_NAMES);
+ Preconditions.checkArgumentNonnegative(beginTimeMillis);
+ Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+ Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+ }
+
+ @Override
+ public void getHistoricalOps(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback) {
+ PackageManager pm = mContext.getPackageManager();
+
+ ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
+ Objects.requireNonNull(callback, "callback cannot be null");
+ ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
+ boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+ if (!isSelfRequest) {
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+ boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+ boolean isCallerPermissionController;
+ try {
+ isCallerPermissionController = pm.getPackageUidAsUser(
+ mContext.getPackageManager().getPermissionControllerPackageName(), 0,
+ UserHandle.getUserId(Binder.getCallingUid()))
+ == Binder.getCallingUid();
+ } catch (PackageManager.NameNotFoundException doesNotHappen) {
+ return;
+ }
+
+ boolean doesCallerHavePermission = mContext.checkPermission(
+ android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
+
+ if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
+ && !doesCallerHavePermission) {
+ mHandler.post(() -> callback.sendResult(new Bundle()));
+ return;
+ }
+
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+ }
+
+ final String[] opNamesArray = (opNames != null)
+ ? opNames.toArray(new String[opNames.size()]) : null;
+
+ Set<String> attributionChainExemptPackages = null;
+ if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+ attributionChainExemptPackages =
+ PermissionManager.getIndicatorExemptedPackages(mContext);
+ }
+
+ final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+ ? attributionChainExemptPackages.toArray(
+ new String[attributionChainExemptPackages.size()]) : null;
+
+ // Must not hold the appops lock
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+ mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+ filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+ callback).recycleOnUse());
+ }
+
+ @Override
+ public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback) {
+ ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+
+ final String[] opNamesArray = (opNames != null)
+ ? opNames.toArray(new String[opNames.size()]) : null;
+
+ Set<String> attributionChainExemptPackages = null;
+ if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+ attributionChainExemptPackages =
+ PermissionManager.getIndicatorExemptedPackages(mContext);
+ }
+
+ final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+ ? attributionChainExemptPackages.toArray(
+ new String[attributionChainExemptPackages.size()]) : null;
+
+ // Must not hold the appops lock
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+ mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+ filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+ callback).recycleOnUse());
+ }
+
+ @Override
+ public void reloadNonHistoricalState() {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
+ writeState();
+ readState();
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ null, uidState.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ private void pruneOpLocked(Op op, int uid, String packageName) {
+ op.removeAttributionsWithNoTime();
+
+ if (op.mAttributions.isEmpty()) {
+ Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
+ if (ops != null) {
+ ops.remove(op.op);
+ op.setMode(AppOpsManager.opToDefaultMode(op.op));
+ if (ops.size() <= 0) {
+ UidState uidState = ops.uidState;
+ ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+ if (pkgOps != null) {
+ pkgOps.remove(ops.packageName);
+ mAppOpsServiceInterface.removePackage(ops.packageName,
+ UserHandle.getUserId(uidState.uid));
+ if (pkgOps.isEmpty()) {
+ uidState.pkgOps = null;
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(uid);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+ if (callingPid == Process.myPid()) {
+ return;
+ }
+ final int callingUser = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+ if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+ // Profile owners are allowed to change modes but only for apps
+ // within their user.
+ return;
+ }
+ }
+ }
+ mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ @Override
+ public void setUidMode(int code, int uid, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback) {
+ if (DEBUG) {
+ Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+ + " by uid " + Binder.getCallingUid());
+ }
+
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ verifyIncomingOp(code);
+ code = AppOpsManager.opToSwitch(code);
+
+ if (permissionPolicyCallback == null) {
+ updatePermissionRevokedCompat(uid, code, mode);
+ }
+
+ int previousMode;
+ synchronized (this) {
+ final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState == null) {
+ if (mode == defaultMode) {
+ return;
+ }
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+ if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+ previousMode = uidState.getUidMode(code);
+ } else {
+ // doesn't look right but is legacy behavior.
+ previousMode = MODE_DEFAULT;
+ }
+
+ if (!uidState.setUidMode(code, mode)) {
+ return;
+ }
+ uidState.evalForegroundOps();
+ if (mode != MODE_ERRORED && mode != previousMode) {
+ updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ }
+ }
+
+ notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
+ notifyOpChangedSync(code, uid, null, mode, previousMode);
+ }
+
+ /**
+ * Notify that an op changed for all packages in an uid.
+ *
+ * @param code The op that changed
+ * @param uid The uid the op was changed for
+ * @param onlyForeground Only notify watchers that watch for foreground changes
+ */
+ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+ @Nullable IAppOpsCallback callbackToIgnore) {
+ ModeCallback listenerToIgnore = callbackToIgnore != null
+ ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+ mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+ listenerToIgnore);
+ }
+
+ private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager == null) {
+ // This can only happen during early boot. At this time the permission state and appop
+ // state are in sync
+ return;
+ }
+
+ String[] packageNames = packageManager.getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return;
+ }
+ String packageName = packageNames[0];
+
+ int[] ops = mSwitchedOps.get(switchCode);
+ for (int code : ops) {
+ String permissionName = AppOpsManager.opToPermission(code);
+ if (permissionName == null) {
+ continue;
+ }
+
+ if (packageManager.checkPermission(permissionName, packageName)
+ != PackageManager.PERMISSION_GRANTED) {
+ continue;
+ }
+
+ PermissionInfo permissionInfo;
+ try {
+ permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (!permissionInfo.isRuntime()) {
+ continue;
+ }
+
+ boolean supportsRuntimePermissions = getPackageManagerInternal()
+ .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
+
+ UserHandle user = UserHandle.getUserHandleForUid(uid);
+ boolean isRevokedCompat;
+ if (permissionInfo.backgroundPermission != null) {
+ if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+
+ if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+ Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ + " permission state, this is discouraged and you should revoke the"
+ + " runtime permission instead: uid=" + uid + ", switchCode="
+ + switchCode + ", mode=" + mode + ", permission="
+ + permissionInfo.backgroundPermission);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+ packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ isBackgroundRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+ && mode != AppOpsManager.MODE_FOREGROUND;
+ } else {
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+ }
+
+ if (isRevokedCompat && supportsRuntimePermissions) {
+ Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ + " permission state, this is discouraged and you should revoke the"
+ + " runtime permission instead: uid=" + uid + ", switchCode="
+ + switchCode + ", mode=" + mode + ", permission=" + permissionName);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionName, packageName,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+ int previousMode) {
+ final StorageManagerInternal storageManagerInternal =
+ LocalServices.getService(StorageManagerInternal.class);
+ if (storageManagerInternal != null) {
+ storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
+ }
+ }
+
+ @Override
+ public void setMode(int code, int uid, @NonNull String packageName, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback) {
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return;
+ }
+
+ ArraySet<OnOpModeChangedListener> repCbs = null;
+ code = AppOpsManager.opToSwitch(code);
+
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, null);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Cannot setMode", e);
+ return;
+ }
+
+ int previousMode = MODE_DEFAULT;
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, false);
+ Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
+ if (op != null) {
+ if (op.getMode() != mode) {
+ previousMode = op.getMode();
+ op.setMode(mode);
+
+ if (uidState != null) {
+ uidState.evalForegroundOps();
+ }
+ ArraySet<OnOpModeChangedListener> cbs =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArraySet<>();
+ }
+ repCbs.addAll(cbs);
+ }
+ cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArraySet<>();
+ }
+ repCbs.addAll(cbs);
+ }
+ if (repCbs != null && permissionPolicyCallback != null) {
+ repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
+ }
+ if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+ // If going into the default mode, prune this op
+ // if there is nothing else interesting in it.
+ pruneOpLocked(op, uid, packageName);
+ }
+ scheduleFastWriteLocked();
+ if (mode != MODE_ERRORED) {
+ updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ }
+ }
+ }
+ }
+ if (repCbs != null) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChanged,
+ this, repCbs, code, uid, packageName));
+ }
+
+ notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+ }
+
+ private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
+ int uid, String packageName) {
+ for (int i = 0; i < callbacks.size(); i++) {
+ final OnOpModeChangedListener callback = callbacks.valueAt(i);
+ notifyOpChanged(callback, code, uid, packageName);
+ }
+ }
+
+ private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+ int uid, String packageName) {
+ mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
+ }
+
+ private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+ int op, int uid, String packageName, int previousMode) {
+ boolean duplicate = false;
+ if (reports == null) {
+ reports = new ArrayList<>();
+ } else {
+ final int reportCount = reports.size();
+ for (int j = 0; j < reportCount; j++) {
+ ChangeRec report = reports.get(j);
+ if (report.op == op && report.pkg.equals(packageName)) {
+ duplicate = true;
+ break;
+ }
+ }
+ }
+ if (!duplicate) {
+ reports.add(new ChangeRec(op, uid, packageName, previousMode));
+ }
+
+ return reports;
+ }
+
+ private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+ int op, int uid, String packageName, int previousMode,
+ ArraySet<OnOpModeChangedListener> cbs) {
+ if (cbs == null) {
+ return callbacks;
+ }
+ if (callbacks == null) {
+ callbacks = new HashMap<>();
+ }
+ final int N = cbs.size();
+ for (int i=0; i<N; i++) {
+ OnOpModeChangedListener cb = cbs.valueAt(i);
+ ArrayList<ChangeRec> reports = callbacks.get(cb);
+ ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
+ if (changed != reports) {
+ callbacks.put(cb, changed);
+ }
+ }
+ return callbacks;
+ }
+
+ static final class ChangeRec {
+ final int op;
+ final int uid;
+ final String pkg;
+ final int previous_mode;
+
+ ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
+ op = _op;
+ uid = _uid;
+ pkg = _pkg;
+ previous_mode = _previous_mode;
+ }
+ }
+
+ @Override
+ public void resetAllModes(int reqUserId, String reqPackageName) {
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
+ true, true, "resetAllModes", null);
+
+ int reqUid = -1;
+ if (reqPackageName != null) {
+ try {
+ reqUid = AppGlobals.getPackageManager().getPackageUid(
+ reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+
+ enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
+ ArrayList<ChangeRec> allChanges = new ArrayList<>();
+ synchronized (this) {
+ boolean changed = false;
+ for (int i = mUidStates.size() - 1; i >= 0; i--) {
+ UidState uidState = mUidStates.valueAt(i);
+
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+ final int uidOpCount = opModes.size();
+ for (int j = uidOpCount - 1; j >= 0; j--) {
+ final int code = opModes.keyAt(j);
+ if (AppOpsManager.opAllowsReset(code)) {
+ int previousMode = opModes.valueAt(j);
+ uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+ for (String packageName : getPackagesForUid(uidState.uid)) {
+ callbacks = addCallbacks(callbacks, code, uidState.uid,
+ packageName, previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(code));
+ callbacks = addCallbacks(callbacks, code, uidState.uid,
+ packageName, previousMode, mAppOpsServiceInterface
+ .getPackageModeChangedListeners(packageName));
+
+ allChanges = addChange(allChanges, code, uidState.uid,
+ packageName, previousMode);
+ }
+ }
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ continue;
+ }
+
+ if (reqUserId != UserHandle.USER_ALL
+ && reqUserId != UserHandle.getUserId(uidState.uid)) {
+ // Skip any ops for a different user
+ continue;
+ }
+
+ Map<String, Ops> packages = uidState.pkgOps;
+ Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+ boolean uidChanged = false;
+ while (it.hasNext()) {
+ Map.Entry<String, Ops> ent = it.next();
+ String packageName = ent.getKey();
+ if (reqPackageName != null && !reqPackageName.equals(packageName)) {
+ // Skip any ops for a different package
+ continue;
+ }
+ Ops pkgOps = ent.getValue();
+ for (int j=pkgOps.size()-1; j>=0; j--) {
+ Op curOp = pkgOps.valueAt(j);
+ if (shouldDeferResetOpToDpm(curOp.op)) {
+ deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
+ continue;
+ }
+ if (AppOpsManager.opAllowsReset(curOp.op)
+ && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+ int previousMode = curOp.getMode();
+ curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+ changed = true;
+ uidChanged = true;
+ final int uid = curOp.uidState.uid;
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode, mAppOpsServiceInterface
+ .getPackageModeChangedListeners(packageName));
+
+ allChanges = addChange(allChanges, curOp.op, uid, packageName,
+ previousMode);
+ curOp.removeAttributionsWithNoTime();
+ if (curOp.mAttributions.isEmpty()) {
+ pkgOps.removeAt(j);
+ }
+ }
+ }
+ if (pkgOps.size() == 0) {
+ it.remove();
+ mAppOpsServiceInterface.removePackage(packageName,
+ UserHandle.getUserId(uidState.uid));
+ }
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(uidState.uid);
+ }
+ if (uidChanged) {
+ uidState.evalForegroundOps();
+ }
+ }
+
+ if (changed) {
+ scheduleFastWriteLocked();
+ }
+ }
+ if (callbacks != null) {
+ for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+ : callbacks.entrySet()) {
+ OnOpModeChangedListener cb = ent.getKey();
+ ArrayList<ChangeRec> reports = ent.getValue();
+ for (int i=0; i<reports.size(); i++) {
+ ChangeRec rep = reports.get(i);
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChanged,
+ this, cb, rep.op, rep.uid, rep.pkg));
+ }
+ }
+ }
+
+ int numChanges = allChanges.size();
+ for (int i = 0; i < numChanges; i++) {
+ ChangeRec change = allChanges.get(i);
+ notifyOpChangedSync(change.op, change.uid, change.pkg,
+ AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
+ }
+ }
+
+ private boolean shouldDeferResetOpToDpm(int op) {
+ // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+ // pre-grants to a role-based mechanism or another general-purpose mechanism.
+ return dpmi != null && dpmi.supportsResetOp(op);
+ }
+
+ /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
+ private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
+ // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+ // pre-grants to a role-based mechanism or another general-purpose mechanism.
+ dpmi.resetOp(op, packageName, userId);
+ }
+
+ private void evalAllForegroundOpsLocked() {
+ for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
+ final UidState uidState = mUidStates.valueAt(uidi);
+ if (uidState.foregroundOps != null) {
+ uidState.evalForegroundOps();
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingModeWithFlags(int op, String packageName, int flags,
+ IAppOpsCallback callback) {
+ int watchedUid = -1;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ // TODO: should have a privileged permission to protect this.
+ // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
+ // the USAGE_STATS permission since this can provide information about when an
+ // app is in the foreground?
+ Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
+ AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
+ if (callback == null) {
+ return;
+ }
+ final boolean mayWatchPackageName = packageName != null
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
+ synchronized (this) {
+ int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+ int notifiedOps;
+ if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
+ if (op == OP_NONE) {
+ notifiedOps = ALL_OPS;
+ } else {
+ notifiedOps = op;
+ }
+ } else {
+ notifiedOps = switchOp;
+ }
+
+ ModeCallback cb = mModeWatchers.get(callback.asBinder());
+ if (cb == null) {
+ cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+ callingPid);
+ mModeWatchers.put(callback.asBinder(), cb);
+ }
+ if (switchOp != AppOpsManager.OP_NONE) {
+ mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
+ }
+ if (mayWatchPackageName) {
+ mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
+ }
+ evalAllForegroundOpsLocked();
+ }
+ }
+
+ @Override
+ public void stopWatchingMode(IAppOpsCallback callback) {
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ ModeCallback cb = mModeWatchers.remove(callback.asBinder());
+ if (cb != null) {
+ cb.unlinkToDeath();
+ mAppOpsServiceInterface.removeListener(cb);
+ }
+
+ evalAllForegroundOpsLocked();
+ }
+ }
+
+ @Override
+ public int checkOperation(int code, int uid, String packageName,
+ @Nullable String attributionTag, boolean raw) {
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+ }
+
+ /**
+ * Get the mode of an app-op.
+ *
+ * @param code The code of the op
+ * @param uid The uid of the package the op belongs to
+ * @param packageName The package the op belongs to
+ * @param raw If the raw state of eval-ed state should be checked.
+ * @return The mode of the op
+ */
+ private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean raw) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, null);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "checkOperation", e);
+ return AppOpsManager.opToDefaultMode(code);
+ }
+
+ if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ synchronized (this) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ code = AppOpsManager.opToSwitch(code);
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState != null
+ && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+ final int rawMode = uidState.getUidMode(code);
+ return raw ? rawMode : uidState.evalMode(code, rawMode);
+ }
+ Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
+ if (op == null) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+ return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
+ }
+ }
+
+ @Override
+ public int checkPackage(int uid, String packageName) {
+ Objects.requireNonNull(packageName);
+ try {
+ verifyAndGetBypass(uid, packageName, null);
+ // When the caller is the system, it's possible that the packageName is the special
+ // one (e.g., "root") which isn't actually existed.
+ if (resolveUid(packageName) == uid
+ || (isPackageExisted(packageName)
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ return AppOpsManager.MODE_ERRORED;
+ } catch (SecurityException ignored) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ }
+
+ private boolean isPackageExisted(String packageName) {
+ return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
+ }
+
+ /**
+ * This method will check with PackageManager to determine if the package provided should
+ * be visible to the {@link Binder#getCallingUid()}.
+ *
+ * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+ */
+ private boolean filterAppAccessUnlocked(String packageName, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ return LocalServices.getService(PackageManagerInternal.class)
+ .filterAppAccess(packageName, callingUid, userId);
+ }
+
+ @Override
+ public int noteOperation(int code, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String message) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ }
+
+ @Override
+ public int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+ @Nullable String proxyAttributionTag, @OpFlags int flags) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "noteOperation", e);
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_IGNORED);
+ if (DEBUG) {
+ Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + "flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ return AppOpsManager.MODE_ERRORED;
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ if (attributedOp.isRunning()) {
+ Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ + code + " startTime of in progress event="
+ + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
+ }
+
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ final UidState uidState = ops.uidState;
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_IGNORED);
+ return AppOpsManager.MODE_IGNORED;
+ }
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (uidMode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) {
+ Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ uidMode);
+ return uidMode;
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) {
+ Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ mode);
+ return mode;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "noteOperation: allowing code " + code + " uid " + uid + " package "
+ + packageName + (attributionTag == null ? ""
+ : "." + attributionTag) + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_ALLOWED);
+ attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
+ uidState.getState(),
+ flags);
+
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ @Override
+ public boolean isAttributionTagValid(int uid, @NonNull String packageName,
+ @Nullable String attributionTag,
+ @Nullable String proxyPackageName) {
+ try {
+ return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName)
+ .isAttributionTagValid;
+ } catch (SecurityException ignored) {
+ // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls
+ // when they need the bypass object.
+ return false;
+ }
+ }
+
+ // TODO moltmann: Allow watching for attribution ops
+ @Override
+ public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+ if (ops != null) {
+ Preconditions.checkArrayElementsInRange(ops, 0,
+ AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
+ }
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mActiveWatchers.put(callback.asBinder(), callbacks);
+ }
+ final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, activeCallback);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingActive(IAppOpsActiveCallback callback) {
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ final SparseArray<ActiveCallback> activeCallbacks =
+ mActiveWatchers.remove(callback.asBinder());
+ if (activeCallbacks == null) {
+ return;
+ }
+ final int callbackCount = activeCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ activeCallbacks.valueAt(i).destroy();
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+ Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+ "Invalid op code in: " + Arrays.toString(ops));
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ synchronized (this) {
+ SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mStartedWatchers.put(callback.asBinder(), callbacks);
+ }
+
+ final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, startedCallback);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingStarted(IAppOpsStartedCallback callback) {
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ synchronized (this) {
+ final SparseArray<StartedCallback> startedCallbacks =
+ mStartedWatchers.remove(callback.asBinder());
+ if (startedCallbacks == null) {
+ return;
+ }
+
+ final int callbackCount = startedCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ startedCallbacks.valueAt(i).destroy();
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+ Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+ "Invalid op code in: " + Arrays.toString(ops));
+ Objects.requireNonNull(callback, "Callback cannot be null");
+ synchronized (this) {
+ SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mNotedWatchers.put(callback.asBinder(), callbacks);
+ }
+ final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, notedCallback);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingNoted(IAppOpsNotedCallback callback) {
+ Objects.requireNonNull(callback, "Callback cannot be null");
+ synchronized (this) {
+ final SparseArray<NotedCallback> notedCallbacks =
+ mNotedWatchers.remove(callback.asBinder());
+ if (notedCallbacks == null) {
+ return;
+ }
+ final int callbackCount = notedCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ notedCallbacks.valueAt(i).destroy();
+ }
+ }
+ }
+
+ @Override
+ public int startOperation(@NonNull IBinder clientId, int code, int uid,
+ @Nullable String packageName, @Nullable String attributionTag,
+ boolean startIfModeDefault, @NonNull String message,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+
+ // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+ // purposes and not as a check, also make sure that the caller is allowed to access
+ // the data gated by OP_RECORD_AUDIO.
+ //
+ // TODO: Revert this change before Android 12.
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+ int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false);
+ if (result != AppOpsManager.MODE_ALLOWED) {
+ return result;
+ }
+ }
+ return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+ Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+ attributionFlags, attributionChainId, /*dryRun*/ false);
+ }
+
+ private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
+ return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+ }
+
+ @Override
+ public int startOperationUnchecked(IBinder clientId, int code, int uid,
+ @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean startIfModeDefault, @AttributionFlags int attributionFlags,
+ int attributionChainId, boolean dryRun) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "startOperation", e);
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ boolean isRestricted;
+ int startType = START_TYPE_FAILED;
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ if (!dryRun) {
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+ attributionChainId);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ return AppOpsManager.MODE_ERRORED;
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final UidState uidState = ops.uidState;
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+ false);
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ if (!dryRun) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, uidMode, startType, attributionFlags, attributionChainId);
+ }
+ return uidMode;
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (!shouldStartForMode(mode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ if (!dryRun) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, mode, startType, attributionFlags, attributionChainId);
+ }
+ return mode;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName + " restricted: " + isRestricted
+ + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ if (!dryRun) {
+ try {
+ if (isRestricted) {
+ attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ } else {
+ attributedOp.started(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ startType = START_TYPE_STARTED;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ attributionChainId);
+ }
+ }
+
+ // Possible bug? The raw mode could have been MODE_DEFAULT to reach here.
+ return isRestricted ? MODE_IGNORED : MODE_ALLOWED;
+ }
+
+ @Override
+ public void finishOperation(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return;
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return;
+ }
+
+ finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ }
+
+ @Override
+ public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Cannot finishOperation", e);
+ return;
+ }
+
+ synchronized (this) {
+ Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
+ pvr.bypass, /* edit */ true);
+ if (op == null) {
+ Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ return;
+ }
+ final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ if (attributedOp == null) {
+ Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ return;
+ }
+
+ if (attributedOp.isRunning() || attributedOp.isPaused()) {
+ attributedOp.finished(clientId);
+ } else {
+ Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ }
+ }
+ }
+
+ void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ ArraySet<ActiveCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mActiveWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
+ ActiveCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpActiveChanged,
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+ attributionFlags, attributionChainId));
+ }
+
+ private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
+ int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+ boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+ // There are features watching for mode changes such as window manager
+ // and location manager which are in our process. The callbacks in these
+ // features may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final ActiveCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+ active, attributionFlags, attributionChainId);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+ String attributionTag, @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ ArraySet<StartedCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mStartedWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
+
+ StartedCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpStarted,
+ this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+ result, startedType, attributionFlags, attributionChainId));
+ }
+
+ private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
+ int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+ @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final StartedCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+ result, startedType, attributionFlags, attributionChainId);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
+ String attributionTag, @OpFlags int flags, @Mode int result) {
+ ArraySet<NotedCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mNotedWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
+ final NotedCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChecked,
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+ result));
+ }
+
+ private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
+ int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+ @Mode int result) {
+ // There are features watching for checks in our process. The callbacks in
+ // these features may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final NotedCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+ result);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void verifyIncomingUid(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+ // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+ // as watcher should not use this to signal if the value is changed.
+ return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void verifyIncomingOp(int op) {
+ if (op >= 0 && op < AppOpsManager._NUM_OP) {
+ // Enforce manage appops permission if it's a restricted read op.
+ if (opRestrictsRead(op)) {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+ }
+ return;
+ }
+ throw new IllegalArgumentException("Bad operation #" + op);
+ }
+
+ private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
+ final int callingUid = Binder.getCallingUid();
+ // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
+ if (packageName == null || isSpecialPackage(callingUid, packageName)) {
+ return true;
+ }
+
+ // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
+ // the end. Although that exception would be caught and return, we could make it return
+ // early.
+ if (!isPackageExisted(packageName)) {
+ return false;
+ }
+
+ if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
+ Slog.w(TAG, packageName + " not found from " + callingUid);
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
+ final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
+ return callingUid == Process.SYSTEM_UID
+ || resolveUid(resolvedPackage) != Process.INVALID_UID;
+ }
+
+ private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ if (!edit) {
+ return null;
+ }
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+
+ return uidState;
+ }
+
+ @Override
+ public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+ synchronized (this) {
+ getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
+ }
+ }
+
+ /**
+ * @return {@link PackageManagerInternal}
+ */
+ private @NonNull PackageManagerInternal getPackageManagerInternal() {
+ if (mPackageManagerInternal == null) {
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ return mPackageManagerInternal;
+ }
+
+ @Override
+ public void verifyPackage(int uid, String packageName) {
+ verifyAndGetBypass(uid, packageName, null);
+ }
+
+ /**
+ * Create a restriction description matching the properties of the package.
+ *
+ * @param pkg The package to create the restriction description for
+ * @return The restriction matching the package
+ */
+ private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
+ return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+ mContext.checkPermission(android.Manifest.permission
+ .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * @see #verifyAndGetBypass(int, String, String, String)
+ */
+ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+ @Nullable String attributionTag) {
+ return verifyAndGetBypass(uid, packageName, attributionTag, null);
+ }
+
+ /**
+ * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
+ * description} for the package, along with a boolean indicating whether the attribution tag is
+ * valid.
+ *
+ * @param uid The uid the package belongs to
+ * @param packageName The package the might belong to the uid
+ * @param attributionTag attribution tag or {@code null} if no need to verify
+ * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+ * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
+ * attribution tag is valid
+ */
+ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+ @Nullable String attributionTag, @Nullable String proxyPackageName) {
+ if (uid == Process.ROOT_UID) {
+ // For backwards compatibility, don't check package name for root UID.
+ return new PackageVerificationResult(null,
+ /* isAttributionTagValid */ true);
+ }
+ if (Process.isSdkSandboxUid(uid)) {
+ // SDK sandbox processes run in their own UID range, but their associated
+ // UID for checks should always be the UID of the package implementing SDK sandbox
+ // service.
+ // TODO: We will need to modify the callers of this function instead, so
+ // modifications and checks against the app ops state are done with the
+ // correct UID.
+ try {
+ final PackageManager pm = mContext.getPackageManager();
+ final String supplementalPackageName = pm.getSdkSandboxPackageName();
+ if (Objects.equals(packageName, supplementalPackageName)) {
+ uid = pm.getPackageUidAsUser(supplementalPackageName,
+ PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Shouldn't happen for the supplemental package
+ e.printStackTrace();
+ }
+ }
+
+
+ // Do not check if uid/packageName/attributionTag is already known.
+ synchronized (this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState != null && uidState.pkgOps != null) {
+ Ops ops = uidState.pkgOps.get(packageName);
+
+ if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
+ attributionTag)) && ops.bypass != null) {
+ return new PackageVerificationResult(ops.bypass,
+ ops.validAttributionTags.contains(attributionTag));
+ }
+ }
+ }
+
+ int callingUid = Binder.getCallingUid();
+
+ // Allow any attribution tag for resolvable uids
+ int pkgUid;
+ if (Objects.equals(packageName, "com.android.shell")) {
+ // Special case for the shell which is a package but should be able
+ // to bypass app attribution tag restrictions.
+ pkgUid = Process.SHELL_UID;
+ } else {
+ pkgUid = resolveUid(packageName);
+ }
+ if (pkgUid != Process.INVALID_UID) {
+ if (pkgUid != UserHandle.getAppId(uid)) {
+ Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+ String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+ throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+ + UserHandle.getAppId(uid) + otherUidMessage);
+ }
+ return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
+ /* isAttributionTagValid */ true);
+ }
+
+ int userId = UserHandle.getUserId(uid);
+ RestrictionBypass bypass = null;
+ boolean isAttributionTagValid = false;
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage pkg = pmInt.getPackage(packageName);
+ if (pkg != null) {
+ isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+ pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+ bypass = getBypassforPackage(pkg);
+ }
+ if (!isAttributionTagValid) {
+ AndroidPackage proxyPkg = proxyPackageName != null
+ ? pmInt.getPackage(proxyPackageName) : null;
+ // Re-check in proxy.
+ isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
+ String msg;
+ if (pkg != null && isAttributionTagValid) {
+ msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+ + " package " + proxyPackageName + ", this is not advised";
+ } else if (pkg != null) {
+ msg = "attributionTag " + attributionTag + " not declared in manifest of "
+ + packageName;
+ } else {
+ msg = "package " + packageName + " not found, can't check for "
+ + "attributionTag " + attributionTag;
+ }
+
+ try {
+ if (!mPlatformCompat.isChangeEnabledByPackageName(
+ SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
+ userId) || !mPlatformCompat.isChangeEnabledByUid(
+ SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
+ callingUid)) {
+ // Do not override tags if overriding is not enabled for this package
+ isAttributionTagValid = true;
+ }
+ Slog.e(TAG, msg);
+ } catch (RemoteException neverHappens) {
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ if (pkgUid != uid) {
+ Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+ String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+ throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+ + otherUidMessage);
+ }
+
+ return new PackageVerificationResult(bypass, isAttributionTagValid);
+ }
+
+ private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+ @Nullable String attributionTag) {
+ if (pkg == null) {
+ return false;
+ } else if (attributionTag == null) {
+ return true;
+ }
+ if (pkg.getAttributions() != null) {
+ int numAttributions = pkg.getAttributions().size();
+ for (int i = 0; i < numAttributions; i++) {
+ if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get (and potentially create) ops.
+ *
+ * @param uid The uid the package belongs to
+ * @param packageName The name of the package
+ * @param attributionTag attribution tag
+ * @param isAttributionTagValid whether the given attribution tag is valid
+ * @param bypass When to bypass certain op restrictions (can be null if edit
+ * == false)
+ * @param edit If an ops does not exist, create the ops?
+ * @return The ops
+ */
+ private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
+ boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
+ UidState uidState = getUidStateLocked(uid, edit);
+ if (uidState == null) {
+ return null;
+ }
+
+ if (uidState.pkgOps == null) {
+ if (!edit) {
+ return null;
+ }
+ uidState.pkgOps = new ArrayMap<>();
+ }
+
+ Ops ops = uidState.pkgOps.get(packageName);
+ if (ops == null) {
+ if (!edit) {
+ return null;
+ }
+ ops = new Ops(packageName, uidState);
+ uidState.pkgOps.put(packageName, ops);
+ }
+
+ if (edit) {
+ if (bypass != null) {
+ ops.bypass = bypass;
+ }
+
+ if (attributionTag != null) {
+ ops.knownAttributionTags.add(attributionTag);
+ if (isAttributionTagValid) {
+ ops.validAttributionTags.add(attributionTag);
+ } else {
+ ops.validAttributionTags.remove(attributionTag);
+ }
+ }
+ }
+
+ return ops;
+ }
+
+ @Override
+ public void scheduleWriteLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+ }
+ }
+
+ @Override
+ public void scheduleFastWriteLocked() {
+ if (!mFastWriteScheduled) {
+ mWriteScheduled = true;
+ mFastWriteScheduled = true;
+ mHandler.removeCallbacks(mWriteRunner);
+ mHandler.postDelayed(mWriteRunner, 10 * 1000);
+ }
+ }
+
+ /**
+ * Get the state of an op for a uid.
+ *
+ * @param code The code of the op
+ * @param uid The uid the of the package
+ * @param packageName The package name for which to get the state for
+ * @param attributionTag The attribution tag
+ * @param isAttributionTagValid Whether the given attribution tag is valid
+ * @param bypass When to bypass certain op restrictions (can be null if edit
+ * == false)
+ * @param edit Iff {@code true} create the {@link Op} object if not yet created
+ * @return The {@link Op state} of the op
+ */
+ private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean isAttributionTagValid,
+ @Nullable RestrictionBypass bypass, boolean edit) {
+ Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
+ edit);
+ if (ops == null) {
+ return null;
+ }
+ return getOpLocked(ops, code, uid, edit);
+ }
+
+ private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
+ Op op = ops.get(code);
+ if (op == null) {
+ if (!edit) {
+ return null;
+ }
+ op = new Op(ops.uidState, ops.packageName, code, uid);
+ ops.put(code, op);
+ }
+ if (edit) {
+ scheduleWriteLocked();
+ }
+ return op;
+ }
+
+ private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+ if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+ return false;
+ }
+ final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+ }
+
+ private boolean isOpRestrictedLocked(int uid, int code, String packageName,
+ String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+ int restrictionSetCount = mOpGlobalRestrictions.size();
+
+ for (int i = 0; i < restrictionSetCount; i++) {
+ ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
+ if (restrictionState.hasRestriction(code)) {
+ return true;
+ }
+ }
+
+ int userHandle = UserHandle.getUserId(uid);
+ restrictionSetCount = mOpUserRestrictions.size();
+
+ for (int i = 0; i < restrictionSetCount; i++) {
+ // For each client, check that the given op is not restricted, or that the given
+ // package is exempt from the restriction.
+ ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+ if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
+ isCheckOp)) {
+ RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
+ if (opBypass != null) {
+ // If we are the system, bypass user restrictions for certain codes
+ synchronized (this) {
+ if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+ return false;
+ }
+ if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
+ return false;
+ }
+ if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
+ && appBypass.isRecordAudioRestrictionExcept) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void readState() {
+ int oldVersion = NO_VERSION;
+ synchronized (mFile) {
+ synchronized (this) {
+ FileInputStream stream;
+ try {
+ stream = mFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ mUidStates.clear();
+ mAppOpsServiceInterface.clearAllModes();
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Parse next until we reach the start or end
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("pkg")) {
+ readPackage(parser);
+ } else if (tagName.equals("uid")) {
+ readUidOps(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <app-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ success = true;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ mUidStates.clear();
+ mAppOpsServiceInterface.clearAllModes();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ synchronized (this) {
+ upgradeLocked(oldVersion);
+ }
+ }
+
+ private void upgradeRunAnyInBackgroundLocked() {
+ for (int i = 0; i < mUidStates.size(); i++) {
+ final UidState uidState = mUidStates.valueAt(i);
+ if (uidState == null) {
+ continue;
+ }
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null) {
+ final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (idx >= 0) {
+ uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+ opModes.valueAt(idx));
+ }
+ }
+ if (uidState.pkgOps == null) {
+ continue;
+ }
+ boolean changed = false;
+ for (int j = 0; j < uidState.pkgOps.size(); j++) {
+ Ops ops = uidState.pkgOps.valueAt(j);
+ if (ops != null) {
+ final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+ final Op copy = new Op(op.uidState, op.packageName,
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+ copy.setMode(op.getMode());
+ ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ uidState.evalForegroundOps();
+ }
+ }
+ }
+
+ private void upgradeLocked(int oldVersion) {
+ if (oldVersion >= CURRENT_VERSION) {
+ return;
+ }
+ Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+ switch (oldVersion) {
+ case NO_VERSION:
+ upgradeRunAnyInBackgroundLocked();
+ // fall through
+ case 1:
+ // for future upgrades
+ }
+ scheduleFastWriteLocked();
+ }
+
+ private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ final int uid = parser.getAttributeInt(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ final int code = parser.getAttributeInt(null, "n");
+ final int mode = parser.getAttributeInt(null, "m");
+ setUidMode(code, uid, mode, null);
+ } else {
+ Slog.w(TAG, "Unknown element under <uid-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readPackage(TypedXmlPullParser parser)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ String pkgName = parser.getAttributeValue(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("uid")) {
+ readUid(parser, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readUid(TypedXmlPullParser parser, String pkgName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int uid = parser.getAttributeInt(null, "n");
+ final UidState uidState = getUidStateLocked(uid, true);
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ readOp(parser, uidState, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ uidState.evalForegroundOps();
+ }
+
+ private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+ @Nullable String attribution)
+ throws NumberFormatException, IOException, XmlPullParserException {
+ final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+
+ final long key = parser.getAttributeLong(null, "n");
+ final int uidState = extractUidStateFromKey(key);
+ final int opFlags = extractFlagsFromKey(key);
+
+ final long accessTime = parser.getAttributeLong(null, "t", 0);
+ final long rejectTime = parser.getAttributeLong(null, "r", 0);
+ final long accessDuration = parser.getAttributeLong(null, "d", -1);
+ final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
+ final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
+ final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
+
+ if (accessTime > 0) {
+ attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+ proxyAttributionTag, uidState, opFlags);
+ }
+ if (rejectTime > 0) {
+ attributedOp.rejected(rejectTime, uidState, opFlags);
+ }
+ }
+
+ private void readOp(TypedXmlPullParser parser,
+ @NonNull UidState uidState, @NonNull String pkgName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int opCode = parser.getAttributeInt(null, "n");
+ Op op = new Op(uidState, pkgName, opCode, uidState.uid);
+
+ final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+ op.setMode(mode);
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("st")) {
+ readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
+ } else {
+ Slog.w(TAG, "Unknown element under <op>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ uidState.pkgOps = new ArrayMap<>();
+ }
+ Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ ops = new Ops(pkgName, uidState);
+ uidState.pkgOps.put(pkgName, ops);
+ }
+ ops.put(op.op, op);
+ }
+
+ @Override
+ public void writeState() {
+ synchronized (mFile) {
+ FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state: " + e);
+ return;
+ }
+
+ List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+ try {
+ TypedXmlSerializer out = Xml.resolveSerializer(stream);
+ out.startDocument(null, true);
+ out.startTag(null, "app-ops");
+ out.attributeInt(null, "v", CURRENT_VERSION);
+
+ SparseArray<SparseIntArray> uidStatesClone;
+ synchronized (this) {
+ uidStatesClone = new SparseArray<>(mUidStates.size());
+
+ final int uidStateCount = mUidStates.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ UidState uidState = mUidStates.valueAt(uidStateNum);
+ int uid = mUidStates.keyAt(uidStateNum);
+
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null && opModes.size() > 0) {
+ uidStatesClone.put(uid, opModes);
+ }
+ }
+ }
+
+ final int uidStateCount = uidStatesClone.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+ if (opModes != null && opModes.size() > 0) {
+ out.startTag(null, "uid");
+ out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+ final int opCount = opModes.size();
+ for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+ final int op = opModes.keyAt(opCountNum);
+ final int mode = opModes.valueAt(opCountNum);
+ out.startTag(null, "op");
+ out.attributeInt(null, "n", op);
+ out.attributeInt(null, "m", mode);
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ }
+
+ if (allOps != null) {
+ String lastPkg = null;
+ for (int i = 0; i < allOps.size(); i++) {
+ AppOpsManager.PackageOps pkg = allOps.get(i);
+ if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ lastPkg = pkg.getPackageName();
+ if (lastPkg != null) {
+ out.startTag(null, "pkg");
+ out.attribute(null, "n", lastPkg);
+ }
+ }
+ out.startTag(null, "uid");
+ out.attributeInt(null, "n", pkg.getUid());
+ List<AppOpsManager.OpEntry> ops = pkg.getOps();
+ for (int j = 0; j < ops.size(); j++) {
+ AppOpsManager.OpEntry op = ops.get(j);
+ out.startTag(null, "op");
+ out.attributeInt(null, "n", op.getOp());
+ if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+ out.attributeInt(null, "m", op.getMode());
+ }
+
+ for (String attributionTag : op.getAttributedOpEntries().keySet()) {
+ final AttributedOpEntry attribution =
+ op.getAttributedOpEntries().get(attributionTag);
+
+ final ArraySet<Long> keys = attribution.collectKeys();
+
+ final int keyCount = keys.size();
+ for (int k = 0; k < keyCount; k++) {
+ final long key = keys.valueAt(k);
+
+ final int uidState = AppOpsManager.extractUidStateFromKey(key);
+ final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+ final long accessTime = attribution.getLastAccessTime(uidState,
+ uidState, flags);
+ final long rejectTime = attribution.getLastRejectTime(uidState,
+ uidState, flags);
+ final long accessDuration = attribution.getLastDuration(
+ uidState, uidState, flags);
+ // Proxy information for rejections is not backed up
+ final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
+ uidState, uidState, flags);
+
+ if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+ && proxy == null) {
+ continue;
+ }
+
+ String proxyPkg = null;
+ String proxyAttributionTag = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyAttributionTag = proxy.getAttributionTag();
+ proxyUid = proxy.getUid();
+ }
+
+ out.startTag(null, "st");
+ if (attributionTag != null) {
+ out.attribute(null, "id", attributionTag);
+ }
+ out.attributeLong(null, "n", key);
+ if (accessTime > 0) {
+ out.attributeLong(null, "t", accessTime);
+ }
+ if (rejectTime > 0) {
+ out.attributeLong(null, "r", rejectTime);
+ }
+ if (accessDuration > 0) {
+ out.attributeLong(null, "d", accessDuration);
+ }
+ if (proxyPkg != null) {
+ out.attribute(null, "pp", proxyPkg);
+ }
+ if (proxyAttributionTag != null) {
+ out.attribute(null, "pc", proxyAttributionTag);
+ }
+ if (proxyUid >= 0) {
+ out.attributeInt(null, "pu", proxyUid);
+ }
+ out.endTag(null, "st");
+ }
+ }
+
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ }
+
+ out.endTag(null, "app-ops");
+ out.endDocument();
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state, restoring backup.", e);
+ mFile.failWrite(stream);
+ }
+ }
+ mHistoricalRegistry.writeAndClearDiscreteHistory();
+ }
+
+ private void dumpHelp(PrintWriter pw) {
+ pw.println("AppOps service (appops) dump options:");
+ pw.println(" -h");
+ pw.println(" Print this help text.");
+ pw.println(" --op [OP]");
+ pw.println(" Limit output to data associated with the given app op code.");
+ pw.println(" --mode [MODE]");
+ pw.println(" Limit output to data associated with the given app op mode.");
+ pw.println(" --package [PACKAGE]");
+ pw.println(" Limit output to data associated with the given package name.");
+ pw.println(" --attributionTag [attributionTag]");
+ pw.println(" Limit output to data associated with the given attribution tag.");
+ pw.println(" --include-discrete [n]");
+ pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit.");
+ pw.println(" --watchers");
+ pw.println(" Only output the watcher sections.");
+ pw.println(" --history");
+ pw.println(" Only output history.");
+ pw.println(" --uid-state-changes");
+ pw.println(" Include logs about uid state changes.");
+ }
+
+ private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
+ @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+ final int numAttributions = op.mAttributions.size();
+ for (int i = 0; i < numAttributions; i++) {
+ if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
+ op.mAttributions.keyAt(i), filterAttributionTag)) {
+ continue;
+ }
+
+ pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
+ dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
+ prefix + " ");
+ pw.print(prefix + "]\n");
+ }
+ }
+
+ private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
+ @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
+ @NonNull Date date, @NonNull String prefix) {
+
+ final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
+ attributionTag).getAttributedOpEntries().get(attributionTag);
+
+ final ArraySet<Long> keys = entry.collectKeys();
+
+ final int keyCount = keys.size();
+ for (int k = 0; k < keyCount; k++) {
+ final long key = keys.valueAt(k);
+
+ final int uidState = AppOpsManager.extractUidStateFromKey(key);
+ final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+ final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+ final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+ final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+ final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+ String proxyPkg = null;
+ String proxyAttributionTag = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyAttributionTag = proxy.getAttributionTag();
+ proxyUid = proxy.getUid();
+ }
+
+ if (accessTime > 0) {
+ pw.print(prefix);
+ pw.print("Access: ");
+ pw.print(AppOpsManager.keyToString(key));
+ pw.print(" ");
+ date.setTime(accessTime);
+ pw.print(sdf.format(date));
+ pw.print(" (");
+ TimeUtils.formatDuration(accessTime - now, pw);
+ pw.print(")");
+ if (accessDuration > 0) {
+ pw.print(" duration=");
+ TimeUtils.formatDuration(accessDuration, pw);
+ }
+ if (proxyUid >= 0) {
+ pw.print(" proxy[");
+ pw.print("uid=");
+ pw.print(proxyUid);
+ pw.print(", pkg=");
+ pw.print(proxyPkg);
+ pw.print(", attributionTag=");
+ pw.print(proxyAttributionTag);
+ pw.print("]");
+ }
+ pw.println();
+ }
+
+ if (rejectTime > 0) {
+ pw.print(prefix);
+ pw.print("Reject: ");
+ pw.print(AppOpsManager.keyToString(key));
+ date.setTime(rejectTime);
+ pw.print(sdf.format(date));
+ pw.print(" (");
+ TimeUtils.formatDuration(rejectTime - now, pw);
+ pw.print(")");
+ if (proxyUid >= 0) {
+ pw.print(" proxy[");
+ pw.print("uid=");
+ pw.print(proxyUid);
+ pw.print(", pkg=");
+ pw.print(proxyPkg);
+ pw.print(", attributionTag=");
+ pw.print(proxyAttributionTag);
+ pw.print("]");
+ }
+ pw.println();
+ }
+ }
+
+ final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ if (attributedOp.isRunning()) {
+ long earliestElapsedTime = Long.MAX_VALUE;
+ long maxNumStarts = 0;
+ int numInProgressEvents = attributedOp.mInProgressEvents.size();
+ for (int i = 0; i < numInProgressEvents; i++) {
+ AttributedOp.InProgressStartOpEvent event =
+ attributedOp.mInProgressEvents.valueAt(i);
+
+ earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
+ maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
+ }
+
+ pw.print(prefix + "Running start at: ");
+ TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
+ pw.println();
+
+ if (maxNumStarts > 1) {
+ pw.print(prefix + "startNesting=");
+ pw.println(maxNumStarts);
+ }
+ }
+ }
+
+ @NeverCompile // Avoid size overhead of debugging code.
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+ int dumpOp = OP_NONE;
+ String dumpPackage = null;
+ String dumpAttributionTag = null;
+ int dumpUid = Process.INVALID_UID;
+ int dumpMode = -1;
+ boolean dumpWatchers = false;
+ // TODO ntmyren: Remove the dumpHistory and dumpFilter
+ boolean dumpHistory = false;
+ boolean includeDiscreteOps = false;
+ boolean dumpUidStateChangeLogs = false;
+ int nDiscreteOps = 10;
+ @HistoricalOpsRequestFilter int dumpFilter = 0;
+ boolean dumpAll = false;
+
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if ("-h".equals(arg)) {
+ dumpHelp(pw);
+ return;
+ } else if ("-a".equals(arg)) {
+ // dump all data
+ dumpAll = true;
+ } else if ("--op".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --op option");
+ return;
+ }
+ dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw);
+ dumpFilter |= FILTER_BY_OP_NAMES;
+ if (dumpOp < 0) {
+ return;
+ }
+ } else if ("--package".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --package option");
+ return;
+ }
+ dumpPackage = args[i];
+ dumpFilter |= FILTER_BY_PACKAGE_NAME;
+ try {
+ dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
+ PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
+ 0);
+ } catch (RemoteException e) {
+ }
+ if (dumpUid < 0) {
+ pw.println("Unknown package: " + dumpPackage);
+ return;
+ }
+ dumpUid = UserHandle.getAppId(dumpUid);
+ dumpFilter |= FILTER_BY_UID;
+ } else if ("--attributionTag".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --attributionTag option");
+ return;
+ }
+ dumpAttributionTag = args[i];
+ dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
+ } else if ("--mode".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --mode option");
+ return;
+ }
+ dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw);
+ if (dumpMode < 0) {
+ return;
+ }
+ } else if ("--watchers".equals(arg)) {
+ dumpWatchers = true;
+ } else if ("--include-discrete".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --include-discrete option");
+ return;
+ }
+ try {
+ nDiscreteOps = Integer.valueOf(args[i]);
+ } catch (NumberFormatException e) {
+ pw.println("Wrong parameter: " + args[i]);
+ return;
+ }
+ includeDiscreteOps = true;
+ } else if ("--history".equals(arg)) {
+ dumpHistory = true;
+ } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+ pw.println("Unknown option: " + arg);
+ return;
+ } else if ("--uid-state-changes".equals(arg)) {
+ dumpUidStateChangeLogs = true;
+ } else {
+ pw.println("Unknown command: " + arg);
+ return;
+ }
+ }
+ }
+
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ final Date date = new Date();
+ synchronized (this) {
+ pw.println("Current AppOps Service state:");
+ if (!dumpHistory && !dumpWatchers) {
+ mConstants.dump(pw);
+ }
+ pw.println();
+ final long now = System.currentTimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ boolean needSep = false;
+ if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+ && !dumpHistory) {
+ pw.println(" Profile owners:");
+ for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+ pw.print(" User #");
+ pw.print(mProfileOwners.keyAt(poi));
+ pw.print(": ");
+ UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+ pw.println();
+ }
+ pw.println();
+ }
+
+ if (!dumpHistory) {
+ needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+ }
+
+ if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+ boolean printedHeader = false;
+ for (int i = 0; i < mModeWatchers.size(); i++) {
+ final ModeCallback cb = mModeWatchers.valueAt(i);
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+ continue;
+ }
+ needSep = true;
+ if (!printedHeader) {
+ pw.println(" All op mode watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
+ pw.print(": ");
+ pw.println(cb);
+ }
+ }
+ if (mActiveWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+ for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
+ final SparseArray<ActiveCallback> activeWatchers =
+ mActiveWatchers.valueAt(watcherNum);
+ if (activeWatchers.size() <= 0) {
+ continue;
+ }
+ final ActiveCallback cb = activeWatchers.valueAt(0);
+ if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" All op active watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mActiveWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+ pw.print(" [");
+ final int opCount = activeWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+ pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (mStartedWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+
+ final int watchersSize = mStartedWatchers.size();
+ for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
+ final SparseArray<StartedCallback> startedWatchers =
+ mStartedWatchers.valueAt(watcherNum);
+ if (startedWatchers.size() <= 0) {
+ continue;
+ }
+
+ final StartedCallback cb = startedWatchers.valueAt(0);
+ if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+
+ if (!printedHeader) {
+ pw.println(" All op started watchers:");
+ printedHeader = true;
+ }
+
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mStartedWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+
+ pw.print(" [");
+ final int opCount = startedWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+
+ pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (mNotedWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+ for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
+ final SparseArray<NotedCallback> notedWatchers =
+ mNotedWatchers.valueAt(watcherNum);
+ if (notedWatchers.size() <= 0) {
+ continue;
+ }
+ final NotedCallback cb = notedWatchers.valueAt(0);
+ if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" All op noted watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mNotedWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+ pw.print(" [");
+ final int opCount = notedWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+ pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (needSep) {
+ pw.println();
+ }
+ for (int i = 0; i < mUidStates.size(); i++) {
+ UidState uidState = mUidStates.valueAt(i);
+ final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+ if (dumpWatchers || dumpHistory) {
+ continue;
+ }
+ if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
+ boolean hasOp = dumpOp < 0 || (opModes != null
+ && opModes.indexOfKey(dumpOp) >= 0);
+ boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
+ boolean hasMode = dumpMode < 0;
+ if (!hasMode && opModes != null) {
+ for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
+ if (opModes.valueAt(opi) == dumpMode) {
+ hasMode = true;
+ }
+ }
+ }
+ if (pkgOps != null) {
+ for (int pkgi = 0;
+ (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+ pkgi++) {
+ Ops ops = pkgOps.valueAt(pkgi);
+ if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
+ hasOp = true;
+ }
+ if (!hasMode) {
+ for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
+ if (ops.valueAt(opi).getMode() == dumpMode) {
+ hasMode = true;
+ }
+ }
+ }
+ if (!hasPackage && dumpPackage.equals(ops.packageName)) {
+ hasPackage = true;
+ }
+ }
+ }
+ if (uidState.foregroundOps != null && !hasOp) {
+ if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
+ hasOp = true;
+ }
+ }
+ if (!hasOp || !hasPackage || !hasMode) {
+ continue;
+ }
+ }
+
+ pw.print(" Uid ");
+ UserHandle.formatUid(pw, uidState.uid);
+ pw.println(":");
+ uidState.dump(pw, nowElapsed);
+ if (uidState.foregroundOps != null && (dumpMode < 0
+ || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
+ pw.println(" foregroundOps:");
+ for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+ if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
+ continue;
+ }
+ pw.print(" ");
+ pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+ pw.print(": ");
+ pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
+ }
+ pw.print(" hasForegroundWatchers=");
+ pw.println(uidState.hasForegroundWatchers);
+ }
+ needSep = true;
+
+ if (opModes != null) {
+ final int opModeCount = opModes.size();
+ for (int j = 0; j < opModeCount; j++) {
+ final int code = opModes.keyAt(j);
+ final int mode = opModes.valueAt(j);
+ if (dumpOp >= 0 && dumpOp != code) {
+ continue;
+ }
+ if (dumpMode >= 0 && dumpMode != mode) {
+ continue;
+ }
+ pw.print(" ");
+ pw.print(AppOpsManager.opToName(code));
+ pw.print(": mode=");
+ pw.println(AppOpsManager.modeToName(mode));
+ }
+ }
+
+ if (pkgOps == null) {
+ continue;
+ }
+
+ for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+ final Ops ops = pkgOps.valueAt(pkgi);
+ if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
+ continue;
+ }
+ boolean printedPackage = false;
+ for (int j = 0; j < ops.size(); j++) {
+ final Op op = ops.valueAt(j);
+ final int opCode = op.op;
+ if (dumpOp >= 0 && dumpOp != opCode) {
+ continue;
+ }
+ if (dumpMode >= 0 && dumpMode != op.getMode()) {
+ continue;
+ }
+ if (!printedPackage) {
+ pw.print(" Package ");
+ pw.print(ops.packageName);
+ pw.println(":");
+ printedPackage = true;
+ }
+ pw.print(" ");
+ pw.print(AppOpsManager.opToName(opCode));
+ pw.print(" (");
+ pw.print(AppOpsManager.modeToName(op.getMode()));
+ final int switchOp = AppOpsManager.opToSwitch(opCode);
+ if (switchOp != opCode) {
+ pw.print(" / switch ");
+ pw.print(AppOpsManager.opToName(switchOp));
+ final Op switchObj = ops.get(switchOp);
+ int mode = switchObj == null
+ ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
+ pw.print("=");
+ pw.print(AppOpsManager.modeToName(mode));
+ }
+ pw.println("): ");
+ dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
+ sdf, date, " ");
+ }
+ }
+ }
+ if (needSep) {
+ pw.println();
+ }
+
+ boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+ mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
+
+ if (dumpAll || dumpUidStateChangeLogs) {
+ pw.println();
+ pw.println("Uid State Changes Event Log:");
+ getUidStateTracker().dumpEvents(pw);
+ }
+ }
+
+ // Must not hold the appops lock
+ if (dumpHistory && !dumpWatchers) {
+ mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
+ dumpFilter);
+ }
+ if (includeDiscreteOps) {
+ pw.println("Discrete accesses: ");
+ mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
+ dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
+ }
+ }
+
+ @Override
+ public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
+ checkSystemUid("setUserRestrictions");
+ Objects.requireNonNull(restrictions);
+ Objects.requireNonNull(token);
+ for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
+ String restriction = AppOpsManager.opToRestriction(i);
+ if (restriction != null) {
+ setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
+ userHandle, null);
+ }
+ }
+ }
+
+ @Override
+ public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+ PackageTagsList excludedPackageTags) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+ if (userHandle != UserHandle.getCallingUserId()) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+ + " INTERACT_ACROSS_USERS to interact cross user ");
+ }
+ }
+ verifyIncomingOp(code);
+ Objects.requireNonNull(token);
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+ }
+
+ private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+ int userHandle, PackageTagsList excludedPackageTags) {
+ synchronized (AppOpsServiceImpl.this) {
+ ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
+
+ if (restrictionState == null) {
+ try {
+ restrictionState = new ClientUserRestrictionState(token);
+ } catch (RemoteException e) {
+ return;
+ }
+ mOpUserRestrictions.put(token, restrictionState);
+ }
+
+ if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
+ userHandle)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+ restricted, userHandle));
+ }
+
+ if (restrictionState.isDefault()) {
+ mOpUserRestrictions.remove(token);
+ restrictionState.destroy();
+ }
+ }
+ }
+
+ @Override
+ public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ throw new SecurityException("Only the system can set global restrictions");
+ }
+
+ synchronized (this) {
+ ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
+
+ if (restrictionState == null) {
+ try {
+ restrictionState = new ClientGlobalRestrictionState(token);
+ } catch (RemoteException e) {
+ return;
+ }
+ mOpGlobalRestrictions.put(token, restrictionState);
+ }
+
+ if (restrictionState.setRestriction(code, restricted)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+ restricted, UserHandle.USER_ALL));
+ }
+
+ if (restrictionState.isDefault()) {
+ mOpGlobalRestrictions.remove(token);
+ restrictionState.destroy();
+ }
+ }
+ }
+
+ @Override
+ public int getOpRestrictionCount(int code, UserHandle user, String pkg,
+ String attributionTag) {
+ int number = 0;
+ synchronized (this) {
+ int numRestrictions = mOpUserRestrictions.size();
+ for (int i = 0; i < numRestrictions; i++) {
+ if (mOpUserRestrictions.valueAt(i)
+ .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
+ false)) {
+ number++;
+ }
+ }
+
+ numRestrictions = mOpGlobalRestrictions.size();
+ for (int i = 0; i < numRestrictions; i++) {
+ if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
+ number++;
+ }
+ }
+ }
+
+ return number;
+ }
+
+ private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+ synchronized (AppOpsServiceImpl.this) {
+ int numUids = mUidStates.size();
+ for (int uidNum = 0; uidNum < numUids; uidNum++) {
+ int uid = mUidStates.keyAt(uidNum);
+ if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+ continue;
+ }
+ updateStartedOpModeForUidLocked(code, restricted, uid);
+ }
+ }
+ }
+
+ private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+
+ int numPkgOps = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+ Op op = ops != null ? ops.get(code) : null;
+ if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
+ continue;
+ }
+ int numAttrTags = op.mAttributions.size();
+ for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+ AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+ if (restricted && attrOp.isRunning()) {
+ attrOp.pause();
+ } else if (attrOp.isPaused()) {
+ attrOp.resume();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void notifyWatchersOfChange(int code, int uid) {
+ final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+ synchronized (this) {
+ modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (modeChangedListenerSet == null) {
+ return;
+ }
+ }
+
+ notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ }
+
+ @Override
+ public void removeUser(int userHandle) throws RemoteException {
+ checkSystemUid("removeUser");
+ synchronized (AppOpsServiceImpl.this) {
+ final int tokenCount = mOpUserRestrictions.size();
+ for (int i = tokenCount - 1; i >= 0; i--) {
+ ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+ opRestrictions.removeUser(userHandle);
+ }
+ removeUidsForUserLocked(userHandle);
+ }
+ }
+
+ @Override
+ public boolean isOperationActive(int code, int uid, String packageName) {
+ if (Binder.getCallingUid() != uid) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return false;
+ }
+
+ final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return false;
+ }
+ // TODO moltmann: Allow to check for attribution op activeness
+ synchronized (AppOpsServiceImpl.this) {
+ Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
+ if (pkgOps == null) {
+ return false;
+ }
+
+ Op op = pkgOps.get(code);
+ if (op == null) {
+ return false;
+ }
+
+ return op.isRunning();
+ }
+ }
+
+ @Override
+ public boolean isProxying(int op, @NonNull String proxyPackageName,
+ @NonNull String proxyAttributionTag, int proxiedUid,
+ @NonNull String proxiedPackageName) {
+ Objects.requireNonNull(proxyPackageName);
+ Objects.requireNonNull(proxiedPackageName);
+ final long callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+ proxiedPackageName, new int[]{op});
+ if (packageOps == null || packageOps.isEmpty()) {
+ return false;
+ }
+ final List<OpEntry> opEntries = packageOps.get(0).getOps();
+ if (opEntries.isEmpty()) {
+ return false;
+ }
+ final OpEntry opEntry = opEntries.get(0);
+ if (!opEntry.isRunning()) {
+ return false;
+ }
+ final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+ OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+ return proxyInfo != null && callingUid == proxyInfo.getUid()
+ && proxyPackageName.equals(proxyInfo.getPackageName())
+ && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void resetPackageOpsNoHistory(@NonNull String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetPackageOpsNoHistory");
+ synchronized (AppOpsServiceImpl.this) {
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+ UserHandle.getCallingUserId());
+ if (uid == Process.INVALID_UID) {
+ return;
+ }
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ Ops removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+
+ @Override
+ public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+ long baseSnapshotInterval, int compressionStep) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "setHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ }
+
+ @Override
+ public void offsetHistory(long offsetMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "offsetHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.offsetHistory(offsetMillis);
+ mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+ }
+
+ @Override
+ public void addHistoricalOps(HistoricalOps ops) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "addHistoricalOps");
+ // Must not hold the appops lock
+ mHistoricalRegistry.addHistoricalOps(ops);
+ }
+
+ @Override
+ public void resetHistoryParameters() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.resetHistoryParameters();
+ }
+
+ @Override
+ public void clearHistory() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "clearHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.clearAllHistory();
+ }
+
+ @Override
+ public void rebootHistory(long offlineDurationMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "rebootHistory");
+
+ Preconditions.checkArgument(offlineDurationMillis >= 0);
+
+ // Must not hold the appops lock
+ mHistoricalRegistry.shutdown();
+
+ if (offlineDurationMillis > 0) {
+ SystemClock.sleep(offlineDurationMillis);
+ }
+
+ mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
+ mHistoricalRegistry.persistPendingHistory();
+ }
+
+ @GuardedBy("this")
+ private void removeUidsForUserLocked(int userHandle) {
+ for (int i = mUidStates.size() - 1; i >= 0; --i) {
+ final int uid = mUidStates.keyAt(i);
+ if (UserHandle.getUserId(uid) == userHandle) {
+ mUidStates.valueAt(i).clear();
+ mUidStates.removeAt(i);
+ }
+ }
+ }
+
+ private void checkSystemUid(String function) {
+ int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new SecurityException(function + " must by called by the system");
+ }
+ }
+
+ private static int resolveUid(String packageName) {
+ if (packageName == null) {
+ return Process.INVALID_UID;
+ }
+ switch (packageName) {
+ case "root":
+ return Process.ROOT_UID;
+ case "shell":
+ case "dumpstate":
+ return Process.SHELL_UID;
+ case "media":
+ return Process.MEDIA_UID;
+ case "audioserver":
+ return Process.AUDIOSERVER_UID;
+ case "cameraserver":
+ return Process.CAMERASERVER_UID;
+ }
+ return Process.INVALID_UID;
+ }
+
+ private static String[] getPackagesForUid(int uid) {
+ String[] packageNames = null;
+
+ // Very early during boot the package manager is not yet or not yet fully started. At this
+ // time there are no packages yet.
+ if (AppGlobals.getPackageManager() != null) {
+ try {
+ packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+ if (packageNames == null) {
+ return EmptyArray.STRING;
+ }
+ return packageNames;
+ }
+
+ private final class ClientUserRestrictionState implements DeathRecipient {
+ private final IBinder mToken;
+
+ ClientUserRestrictionState(IBinder token)
+ throws RemoteException {
+ token.linkToDeath(this, 0);
+ this.mToken = token;
+ }
+
+ public boolean setRestriction(int code, boolean restricted,
+ PackageTagsList excludedPackageTags, int userId) {
+ return mAppOpsRestrictions.setUserRestriction(mToken, userId, code,
+ restricted, excludedPackageTags);
+ }
+
+ public boolean hasRestriction(int code, String packageName, String attributionTag,
+ int userId, boolean isCheckOp) {
+ return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName,
+ attributionTag, isCheckOp);
+ }
+
+ public void removeUser(int userId) {
+ mAppOpsRestrictions.clearUserRestrictions(mToken, userId);
+ }
+
+ public boolean isDefault() {
+ return !mAppOpsRestrictions.hasUserRestrictions(mToken);
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (AppOpsServiceImpl.this) {
+ mAppOpsRestrictions.clearUserRestrictions(mToken);
+ mOpUserRestrictions.remove(mToken);
+ destroy();
+ }
+ }
+
+ public void destroy() {
+ mToken.unlinkToDeath(this, 0);
+ }
+ }
+
+ private final class ClientGlobalRestrictionState implements DeathRecipient {
+ final IBinder mToken;
+
+ ClientGlobalRestrictionState(IBinder token)
+ throws RemoteException {
+ token.linkToDeath(this, 0);
+ this.mToken = token;
+ }
+
+ boolean setRestriction(int code, boolean restricted) {
+ return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
+ }
+
+ boolean hasRestriction(int code) {
+ return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
+ }
+
+ boolean isDefault() {
+ return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
+ }
+
+ @Override
+ public void binderDied() {
+ mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+ mOpGlobalRestrictions.remove(mToken);
+ destroy();
+ }
+
+ void destroy() {
+ mToken.unlinkToDeath(this, 0);
+ }
+ }
+
+ @Override
+ public void setDeviceAndProfileOwners(SparseIntArray owners) {
+ synchronized (this) {
+ mProfileOwners = owners;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index 18f659e..8420fcb 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -13,197 +13,482 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.server.appop;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager.Mode;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.AttributionSource;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PackageTagsList;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.SparseArray;
import android.util.SparseIntArray;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/**
- * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
- * This interface also includes functions for added and removing op mode watchers.
- * In the future this interface will also include op restrictions.
+ *
*/
-public interface AppOpsServiceInterface {
- /**
- * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
- * Returns an empty SparseIntArray if nothing is set.
- * @param uid for which we need the app-ops and their modes.
- */
- SparseIntArray getNonDefaultUidModes(int uid);
+public interface AppOpsServiceInterface extends PersistenceScheduler {
/**
- * Returns the app-op mode for a particular app-op of a uid.
- * Returns default op mode if the op mode for particular uid and op is not set.
- * @param uid user id for which we need the mode.
- * @param op app-op for which we need the mode.
- * @return mode of the app-op.
- */
- int getUidMode(int uid, int op);
-
- /**
- * Set the app-op mode for a particular uid and op.
- * The mode is not set if the mode is the same as the default mode for the op.
- * @param uid user id for which we want to set the mode.
- * @param op app-op for which we want to set the mode.
- * @param mode mode for the app-op.
- * @return true if op mode is changed.
- */
- boolean setUidMode(int uid, int op, @Mode int mode);
-
- /**
- * Gets the app-op mode for a particular package.
- * Returns default op mode if the op mode for the particular package is not set.
- * @param packageName package name for which we need the op mode.
- * @param op app-op for which we need the mode.
- * @param userId user id associated with the package.
- * @return the mode of the app-op.
- */
- int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
-
- /**
- * Sets the app-op mode for a particular package.
- * @param packageName package name for which we need to set the op mode.
- * @param op app-op for which we need to set the mode.
- * @param mode the mode of the app-op.
- * @param userId user id associated with the package.
*
*/
- void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+ void systemReady();
/**
- * Stop tracking any app-op modes for a package.
- * @param packageName Name of the package for which we want to remove all mode tracking.
- * @param userId user id associated with the package.
+ *
*/
- boolean removePackage(@NonNull String packageName, @UserIdInt int userId);
+ void shutdown();
/**
- * Stop tracking any app-op modes for this uid.
- * @param uid user id for which we want to remove all tracking.
+ *
+ * @param uid
+ * @param packageName
*/
- void removeUid(int uid);
+ void verifyPackage(int uid, String packageName);
/**
- * Returns true if all uid modes for this uid are
- * in default state.
- * @param uid user id
+ *
+ * @param op
+ * @param packageName
+ * @param flags
+ * @param callback
*/
- boolean areUidModesDefault(int uid);
+ void startWatchingModeWithFlags(int op, String packageName, int flags,
+ IAppOpsCallback callback);
/**
- * Returns true if all package modes for this package name are
- * in default state.
- * @param packageName package name.
- * @param userId user id associated with the package.
+ *
+ * @param callback
*/
- boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+ void stopWatchingMode(IAppOpsCallback callback);
/**
- * Stop tracking app-op modes for all uid and packages.
+ *
+ * @param ops
+ * @param callback
*/
- void clearAllModes();
+ void startWatchingActive(int[] ops, IAppOpsActiveCallback callback);
/**
- * Registers changedListener to listen to op's mode change.
- * @param changedListener the listener that must be trigger on the op's mode change.
- * @param op op representing the app-op whose mode change needs to be listened to.
+ *
+ * @param callback
*/
- void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+ void stopWatchingActive(IAppOpsActiveCallback callback);
/**
- * Registers changedListener to listen to package's app-op's mode change.
- * @param changedListener the listener that must be trigger on the mode change.
- * @param packageName of the package whose app-op's mode change needs to be listened to.
+ *
+ * @param ops
+ * @param callback
*/
- void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
- @NonNull String packageName);
+ void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback);
/**
- * Stop the changedListener from triggering on any mode change.
- * @param changedListener the listener that needs to be removed.
+ *
+ * @param callback
*/
- void removeListener(@NonNull OnOpModeChangedListener changedListener);
+ void stopWatchingStarted(IAppOpsStartedCallback callback);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
- * @param op app-op whose mode change is being listened to.
+ *
+ * @param ops
+ * @param callback
*/
- ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+ void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
- * @param packageName of package whose app-op's mode change is being listened to.
+ *
+ * @param callback
*/
- ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+ void stopWatchingNoted(IAppOpsNotedCallback callback);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Notify that the app-op's mode is changed by triggering the change listener.
- * @param op App-op whose mode has changed
- * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+ * @param clientId
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param startIfModeDefault
+ * @param message
+ * @param attributionFlags
+ * @param attributionChainId
+ * @return
*/
- void notifyWatchersOfChange(int op, int uid);
+ int startOperation(@NonNull IBinder clientId, int code, int uid,
+ @Nullable String packageName, @Nullable String attributionTag,
+ boolean startIfModeDefault, @NonNull String message,
+ @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId);
+
+
+ int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags,
+ boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId, boolean dryRun);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Notify that the app-op's mode is changed by triggering the change listener.
- * @param changedListener the change listener.
- * @param op App-op whose mode has changed
- * @param uid user id associated with the app-op
- * @param packageName package name that is associated with the app-op
+ *
+ * @param clientId
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
*/
- void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
- @Nullable String packageName);
+ void finishOperation(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Notify that the app-op's mode is changed to all packages associated with the uid by
- * triggering the appropriate change listener.
- * @param op App-op whose mode has changed
- * @param uid user id associated with the app-op
- * @param onlyForeground true if only watchers that
- * @param callbackToIgnore callback that should be ignored.
+ *
+ * @param clientId
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
*/
- void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
- @Nullable OnOpModeChangedListener callbackToIgnore);
+ void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag);
/**
- * TODO: Move hasForegroundWatchers and foregroundOps into this.
- * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
- * foregroundOps.
- * @param uid for which the app-op's mode needs to be marked.
- * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
- * @return foregroundOps.
+ *
+ * @param uidPackageNames
+ * @param visible
*/
- SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+ void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible);
/**
- * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
- * foregroundOps.
- * @param packageName for which the app-op's mode needs to be marked.
- * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
- * @param userId user id associated with the package.
- * @return foregroundOps.
+ *
*/
- SparseBooleanArray evalForegroundPackageOps(String packageName,
- SparseBooleanArray foregroundOps, @UserIdInt int userId);
+ void readState();
/**
- * Dump op mode and package mode listeners and their details.
- * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
- * app-op, only the watchers for that app-op are dumped.
- * @param dumpUid uid for which we want to dump op mode watchers.
- * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
- * @param printWriter writer to dump to.
+ *
*/
- boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+ void writeState();
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ */
+ void packageRemoved(int uid, String packageName);
+
+ /**
+ *
+ * @param uid
+ */
+ void uidRemoved(int uid);
+
+ /**
+ *
+ * @param uid
+ * @param procState
+ * @param capability
+ */
+ void updateUidProcState(int uid, int procState,
+ @ActivityManager.ProcessCapability int capability);
+
+ /**
+ *
+ * @param ops
+ * @return
+ */
+ List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @param ops
+ * @return
+ */
+ List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+ int[] ops);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param opNames
+ * @param dataType
+ * @param filter
+ * @param beginTimeMillis
+ * @param endTimeMillis
+ * @param flags
+ * @param callback
+ */
+ void getHistoricalOps(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param opNames
+ * @param dataType
+ * @param filter
+ * @param beginTimeMillis
+ * @param endTimeMillis
+ * @param flags
+ * @param callback
+ */
+ void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback);
+
+ /**
+ *
+ */
+ void reloadNonHistoricalState();
+
+ /**
+ *
+ * @param uid
+ * @param ops
+ * @return
+ */
+ List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops);
+
+ /**
+ *
+ * @param owners
+ */
+ void setDeviceAndProfileOwners(SparseIntArray owners);
+
+ // used in audio restriction calls, might just copy the logic to avoid having this call.
+ /**
+ *
+ * @param callingPid
+ * @param callingUid
+ * @param targetUid
+ */
+ void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param mode
+ * @param permissionPolicyCallback
+ */
+ void setUidMode(int code, int uid, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param mode
+ * @param permissionPolicyCallback
+ */
+ void setMode(int code, int uid, @NonNull String packageName, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback);
+
+ /**
+ *
+ * @param reqUserId
+ * @param reqPackageName
+ */
+ void resetAllModes(int reqUserId, String reqPackageName);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param raw
+ * @return
+ */
+ int checkOperation(int code, int uid, String packageName,
+ @Nullable String attributionTag, boolean raw);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @return
+ */
+ int checkPackage(int uid, String packageName);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param message
+ * @return
+ */
+ int noteOperation(int code, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String message);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param proxyUid
+ * @param proxyPackageName
+ * @param proxyAttributionTag
+ * @param flags
+ * @return
+ */
+ @AppOpsManager.Mode
+ int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags);
+
+ boolean isAttributionTagValid(int uid, @NonNull String packageName,
+ @Nullable String attributionTag, @Nullable String proxyPackageName);
+
+ /**
+ *
+ * @param fd
+ * @param pw
+ * @param args
+ */
+ @NeverCompile
+ // Avoid size overhead of debugging code.
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+ /**
+ *
+ * @param restrictions
+ * @param token
+ * @param userHandle
+ */
+ void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle);
+
+ /**
+ *
+ * @param code
+ * @param restricted
+ * @param token
+ * @param userHandle
+ * @param excludedPackageTags
+ */
+ void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+ PackageTagsList excludedPackageTags);
+
+ /**
+ *
+ * @param code
+ * @param restricted
+ * @param token
+ */
+ void setGlobalRestriction(int code, boolean restricted, IBinder token);
+
+ /**
+ *
+ * @param code
+ * @param user
+ * @param pkg
+ * @param attributionTag
+ * @return
+ */
+ int getOpRestrictionCount(int code, UserHandle user, String pkg,
+ String attributionTag);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ */
+ // added to interface for audio restriction stuff
+ void notifyWatchersOfChange(int code, int uid);
+
+ /**
+ *
+ * @param userHandle
+ * @throws RemoteException
+ */
+ void removeUser(int userHandle) throws RemoteException;
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @return
+ */
+ boolean isOperationActive(int code, int uid, String packageName);
+
+ /**
+ *
+ * @param op
+ * @param proxyPackageName
+ * @param proxyAttributionTag
+ * @param proxiedUid
+ * @param proxiedPackageName
+ * @return
+ */
+ // TODO this one might not need to be in the interface
+ boolean isProxying(int op, @NonNull String proxyPackageName,
+ @NonNull String proxyAttributionTag, int proxiedUid,
+ @NonNull String proxiedPackageName);
+
+ /**
+ *
+ * @param packageName
+ */
+ void resetPackageOpsNoHistory(@NonNull String packageName);
+
+ /**
+ *
+ * @param mode
+ * @param baseSnapshotInterval
+ * @param compressionStep
+ */
+ void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+ long baseSnapshotInterval, int compressionStep);
+
+ /**
+ *
+ * @param offsetMillis
+ */
+ void offsetHistory(long offsetMillis);
+
+ /**
+ *
+ * @param ops
+ */
+ void addHistoricalOps(AppOpsManager.HistoricalOps ops);
+
+ /**
+ *
+ */
+ void resetHistoryParameters();
+
+ /**
+ *
+ */
+ void clearHistory();
+
+ /**
+ *
+ * @param offlineDurationMillis
+ */
+ void rebootHistory(long offlineDurationMillis);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 5114bd5..c1434e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -59,7 +59,7 @@
private final DelayableExecutor mExecutor;
private final Clock mClock;
private ActivityManagerInternal mActivityManagerInternal;
- private AppOpsService.Constants mConstants;
+ private AppOpsServiceImpl.Constants mConstants;
private SparseIntArray mUidStates = new SparseIntArray();
private SparseIntArray mPendingUidStates = new SparseIntArray();
@@ -85,7 +85,7 @@
AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
Handler handler, Executor lockingExecutor, Clock clock,
- AppOpsService.Constants constants) {
+ AppOpsServiceImpl.Constants constants) {
this(activityManagerInternal, new DelayableExecutor() {
@Override
@@ -102,7 +102,7 @@
@VisibleForTesting
AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
- DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+ DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
Thread executorThread) {
mActivityManagerInternal = activityManagerInternal;
mExecutor = executor;
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index dcc36bc..7970269 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -40,9 +40,9 @@
import java.util.NoSuchElementException;
final class AttributedOp {
- private final @NonNull AppOpsService mAppOpsService;
+ private final @NonNull AppOpsServiceImpl mAppOpsService;
public final @Nullable String tag;
- public final @NonNull AppOpsService.Op parent;
+ public final @NonNull AppOpsServiceImpl.Op parent;
/**
* Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
@@ -80,8 +80,8 @@
// @GuardedBy("mAppOpsService")
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
- AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
- @NonNull AppOpsService.Op parent) {
+ AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag,
+ @NonNull AppOpsServiceImpl.Op parent) {
mAppOpsService = appOpsService;
this.tag = tag;
this.parent = parent;
@@ -131,8 +131,8 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
- proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid,
+ proxyPackageName, proxyAttributionTag);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -238,7 +238,7 @@
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
SystemClock.elapsedRealtime(), clientId, tag,
- PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
+ PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
attributionFlags, attributionChainId);
events.put(clientId, event);
@@ -251,9 +251,9 @@
event.mNumUnfinishedStarts++;
if (isStarted) {
- mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
- parent.packageName, tag, uidState, flags, startTime, attributionFlags,
- attributionChainId);
+ mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+ parent.uid, parent.packageName, tag, uidState, flags, startTime,
+ attributionFlags, attributionChainId);
}
}
@@ -309,8 +309,8 @@
mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
finishedEvent);
- mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
- parent.packageName, tag, event.getUidState(),
+ mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op,
+ parent.uid, parent.packageName, tag, event.getUidState(),
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
event.getAttributionFlags(), event.getAttributionChainId());
@@ -334,13 +334,13 @@
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
if (!isPaused()) {
- Slog.wtf(AppOpsService.TAG, "No ops running or paused");
+ Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
return;
}
int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
if (indexOfToken < 0) {
- Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
+ Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
return;
} else if (isPausing) {
// already paused
@@ -416,9 +416,9 @@
mInProgressEvents.put(event.getClientId(), event);
event.setStartElapsedTime(SystemClock.elapsedRealtime());
event.setStartTime(startTime);
- mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
- parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
- event.getAttributionFlags(), event.getAttributionChainId());
+ mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+ parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(),
+ startTime, event.getAttributionFlags(), event.getAttributionChainId());
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, tag, true, event.getAttributionFlags(),
@@ -503,8 +503,8 @@
newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
}
} catch (RemoteException e) {
- if (AppOpsService.DEBUG) {
- Slog.e(AppOpsService.TAG,
+ if (AppOpsServiceImpl.DEBUG) {
+ Slog.e(AppOpsServiceImpl.TAG,
"Cannot switch to new uidState " + newState);
}
}
@@ -555,8 +555,8 @@
ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
opToAdd.isRunning()
? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
- Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
- + opToAdd.isRunning());
+ Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size()
+ + " app-ops, running: " + opToAdd.isRunning());
int numInProgressEvents = ignoredEvents.size();
for (int i = 0; i < numInProgressEvents; i++) {
@@ -668,16 +668,22 @@
/**
* Create a new {@link InProgressStartOpEvent}.
*
- * @param startTime The time {@link #startOperation} was called
- * @param startElapsedTime The elapsed time when {@link #startOperation} was called
- * @param clientId The client id of the caller of {@link #startOperation}
+ * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation}
+ * was called
+ * @param startElapsedTime The elapsed time whe
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * @param clientId The client id of the caller of
+ * {@link AppOpCheckingServiceInterface#startOperation}
* @param attributionTag The attribution tag for the operation.
* @param onDeath The code to execute on client death
- * @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param uidState The uidstate of the app
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * for
* @param attributionFlags the attribution flags for this operation.
* @param attributionChainId the unique id of the attribution chain this op is a part of.
- * @param proxy The proxy information, if {@link #startProxyOperation} was
- * called
+ * @param proxy The proxy information, if
+ * {@link AppOpCheckingServiceInterface#startProxyOperation} was
+ * called
* @param flags The trusted/nontrusted/self flags.
* @throws RemoteException If the client is dying
*/
@@ -718,15 +724,21 @@
/**
* Reinit existing object with new state.
*
- * @param startTime The time {@link #startOperation} was called
- * @param startElapsedTime The elapsed time when {@link #startOperation} was called
- * @param clientId The client id of the caller of {@link #startOperation}
+ * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation}
+ * was called
+ * @param startElapsedTime The elapsed time when
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * @param clientId The client id of the caller of
+ * {@link AppOpCheckingServiceInterface#startOperation}
* @param attributionTag The attribution tag for this operation.
* @param onDeath The code to execute on client death
- * @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param uidState The uidstate of the app
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * for
* @param flags The flags relating to the proxy
- * @param proxy The proxy information, if {@link #startProxyOperation}
- * was called
+ * @param proxy The proxy information, if
+ * {@link AppOpCheckingServiceInterface#startProxyOperation was
+ * called
* @param attributionFlags the attribution flags for this operation.
* @param attributionChainId the unique id of the attribution chain this op is a part of.
* @param proxyPool The pool to release
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ae929c4..8aa898e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5863,6 +5863,9 @@
};
private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+ if (!device.isSink()) {
+ return false;
+ }
for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
if (device.getType() == type) {
return true;
@@ -5897,7 +5900,11 @@
throw new IllegalArgumentException("invalid portID " + portId);
}
if (!isValidCommunicationDevice(device)) {
- throw new IllegalArgumentException("invalid device type " + device.getType());
+ if (!device.isSink()) {
+ throw new IllegalArgumentException("device must have sink role");
+ } else {
+ throw new IllegalArgumentException("invalid device type: " + device.getType());
+ }
}
}
final String eventSource = new StringBuilder()
@@ -7092,9 +7099,10 @@
private @AudioManager.DeviceVolumeBehavior
int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
- // translate Java device type to native device type (for the devices masks for full / fixed)
- final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
- device.getType());
+ // Get the internal type set by the AudioDeviceAttributes constructor which is always more
+ // exact (avoids double conversions) than a conversion from SDK type via
+ // AudioDeviceInfo.convertDeviceTypeToInternalDevice()
+ final int audioSystemDeviceOut = device.getInternalType();
int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 84dfe86..a0cbd7f 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -158,6 +158,19 @@
public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 16;
/**
+ * Flag: Indicates that the display maintains its own focus and touch mode.
+ *
+ * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+ * behavior, but only applies to the specific display instead of system-wide to all displays.
+ *
+ * Note: The display must be trusted in order to have its own focus.
+ *
+ * @see #FLAG_TRUSTED
+ * @hide
+ */
+ public static final int FLAG_OWN_FOCUS = 1 << 17;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
@@ -584,9 +597,30 @@
if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
}
+ if ((flags & FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+ msg.append(", FLAG_DESTROY_CONTENT_ON_REMOVAL");
+ }
if ((flags & FLAG_MASK_DISPLAY_CUTOUT) != 0) {
msg.append(", FLAG_MASK_DISPLAY_CUTOUT");
}
+ if ((flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+ msg.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS");
+ }
+ if ((flags & FLAG_TRUSTED) != 0) {
+ msg.append(", FLAG_TRUSTED");
+ }
+ if ((flags & FLAG_OWN_DISPLAY_GROUP) != 0) {
+ msg.append(", FLAG_OWN_DISPLAY_GROUP");
+ }
+ if ((flags & FLAG_ALWAYS_UNLOCKED) != 0) {
+ msg.append(", FLAG_ALWAYS_UNLOCKED");
+ }
+ if ((flags & FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+ msg.append(", FLAG_TOUCH_FEEDBACK_DISABLED");
+ }
+ if ((flags & FLAG_OWN_FOCUS) != 0) {
+ msg.append(", FLAG_OWN_FOCUS");
+ }
return msg.toString();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 05cd67f..c5cb08d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -105,7 +105,6 @@
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.sysprop.DisplayProperties;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.EventLog;
@@ -451,8 +450,6 @@
}
};
- private final boolean mAllowNonNativeRefreshRateOverride;
-
private final BrightnessSynchronizer mBrightnessSynchronizer;
/**
@@ -506,7 +503,6 @@
ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
mWideColorSpace = colorSpaces[1];
mOverlayProperties = SurfaceControl.getOverlaySupport();
- mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
mSystemReady = false;
}
@@ -930,24 +926,20 @@
}
}
- if (mAllowNonNativeRefreshRateOverride) {
- overriddenInfo.refreshRateOverride = frameRateHz;
- if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
- callingUid)) {
- overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
- info.supportedModes.length + 1);
- overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
- new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
- currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
- overriddenInfo.refreshRateOverride);
- overriddenInfo.modeId =
- overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
- .getModeId();
- }
- return overriddenInfo;
+ overriddenInfo.refreshRateOverride = frameRateHz;
+ if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+ callingUid)) {
+ overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+ info.supportedModes.length + 1);
+ overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+ new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+ currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+ overriddenInfo.refreshRateOverride);
+ overriddenInfo.modeId =
+ overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+ .getModeId();
}
-
- return info;
+ return overriddenInfo;
}
private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
@@ -2602,11 +2594,6 @@
long getDefaultDisplayDelayTimeout() {
return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
}
-
- boolean getAllowNonNativeRefreshRateOverride() {
- return DisplayProperties
- .debug_allow_non_native_refresh_rate_override().orElse(true);
- }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dedc56a..26ac528 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -384,6 +384,9 @@
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED;
}
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) {
+ mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS;
+ }
Rect maskingInsets = getMaskingInsets(deviceInfo);
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 20b82c3..a23a073 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -21,6 +21,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
@@ -495,13 +496,25 @@
if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
mInfo.flags |= FLAG_TRUSTED;
}
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0
- && (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
- mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
+ mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+ } else {
+ Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it "
+ + "requires VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
+ }
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED;
}
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) {
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS;
+ } else {
+ Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_OWN_FOCUS as it requires "
+ + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
+ }
+ }
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 6dbb362..4d67311 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -42,12 +42,15 @@
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.UnbindReason;
import com.android.server.EventLogTags;
import com.android.server.wm.WindowManagerInternal;
+import java.util.concurrent.CountDownLatch;
+
/**
* A controller managing the state of the input method binding.
*/
@@ -77,19 +80,26 @@
@GuardedBy("ImfLock.class") private boolean mVisibleBound;
@GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
+ @Nullable private CountDownLatch mLatchForTesting;
+
/**
* Binding flags for establishing connection to the {@link InputMethodService}.
*/
- private static final int IME_CONNECTION_BIND_FLAGS =
+ @VisibleForTesting
+ static final int IME_CONNECTION_BIND_FLAGS =
Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE
| Context.BIND_NOT_FOREGROUND
| Context.BIND_IMPORTANT_BACKGROUND
| Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
+ private final int mImeConnectionBindFlags;
+
/**
* Binding flags used only while the {@link InputMethodService} is showing window.
*/
- private static final int IME_VISIBLE_BIND_FLAGS =
+ @VisibleForTesting
+ static final int IME_VISIBLE_BIND_FLAGS =
Context.BIND_AUTO_CREATE
| Context.BIND_TREAT_LIKE_ACTIVITY
| Context.BIND_FOREGROUND_SERVICE
@@ -97,12 +107,19 @@
| Context.BIND_SHOWING_UI;
InputMethodBindingController(@NonNull InputMethodManagerService service) {
+ this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
+ }
+
+ InputMethodBindingController(@NonNull InputMethodManagerService service,
+ int imeConnectionBindFlags, CountDownLatch latchForTesting) {
mService = service;
mContext = mService.mContext;
mMethodMap = mService.mMethodMap;
mSettings = mService.mSettings;
mPackageManagerInternal = mService.mPackageManagerInternal;
mWindowManagerInternal = mService.mWindowManagerInternal;
+ mImeConnectionBindFlags = imeConnectionBindFlags;
+ mLatchForTesting = latchForTesting;
}
/**
@@ -242,7 +259,7 @@
@Override public void onBindingDied(ComponentName name) {
synchronized (ImfLock.class) {
mService.invalidateAutofillSessionLocked();
- if (mVisibleBound) {
+ if (isVisibleBound()) {
unbindVisibleConnection();
}
}
@@ -291,6 +308,10 @@
mService.scheduleResetStylusHandwriting();
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+ if (mLatchForTesting != null) {
+ mLatchForTesting.countDown(); // Notify the finish to tests
+ }
}
@GuardedBy("ImfLock.class")
@@ -338,15 +359,15 @@
@GuardedBy("ImfLock.class")
void unbindCurrentMethod() {
- if (mVisibleBound) {
+ if (isVisibleBound()) {
unbindVisibleConnection();
}
- if (mHasConnection) {
+ if (hasConnection()) {
unbindMainConnection();
}
- if (mCurToken != null) {
+ if (getCurToken() != null) {
removeCurrentToken();
mService.resetSystemUiLocked();
}
@@ -448,17 +469,17 @@
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
- if (mCurIntent == null || conn == null) {
+ if (getCurIntent() == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
return false;
}
- return mContext.bindServiceAsUser(mCurIntent, conn, flags,
+ return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodServiceMainConnection() {
- mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS);
+ mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
return mHasConnection;
}
@@ -472,7 +493,7 @@
void setCurrentMethodVisible() {
if (mCurMethod != null) {
if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
- if (mHasConnection && !mVisibleBound) {
+ if (hasConnection() && !isVisibleBound()) {
mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
IME_VISIBLE_BIND_FLAGS);
}
@@ -480,7 +501,7 @@
}
// No IME is currently connected. Reestablish the main connection.
- if (!mHasConnection) {
+ if (!hasConnection()) {
if (DEBUG) {
Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
}
@@ -512,7 +533,7 @@
*/
@GuardedBy("ImfLock.class")
void setCurrentMethodNotVisible() {
- if (mVisibleBound) {
+ if (isVisibleBound()) {
unbindVisibleConnection();
}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 8d247f6..3ce51c3 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -28,6 +28,7 @@
import static android.location.LocationManager.NETWORK_PROVIDER;
import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER;
+import static android.location.provider.LocationProviderBase.ACTION_GNSS_PROVIDER;
import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -439,9 +440,24 @@
mGnssManagerService = new GnssManagerService(mContext, mInjector, gnssNative);
mGnssManagerService.onSystemReady();
+ boolean useGnssHardwareProvider = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useGnssHardwareProvider);
+ AbstractLocationProvider gnssProvider = null;
+ if (!useGnssHardwareProvider) {
+ gnssProvider = ProxyLocationProvider.create(
+ mContext,
+ GPS_PROVIDER,
+ ACTION_GNSS_PROVIDER,
+ com.android.internal.R.bool.config_useGnssHardwareProvider,
+ com.android.internal.R.string.config_gnssLocationProviderPackageName);
+ }
+ if (gnssProvider == null) {
+ gnssProvider = mGnssManagerService.getGnssLocationProvider();
+ }
+
LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
GPS_PROVIDER, mPassiveManager);
- addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
+ addLocationProviderManager(gnssManager, gnssProvider);
}
// bind to geocoder provider
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index f3cfa95..b8bdabe 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -213,7 +213,7 @@
final int appId = UserHandle.getAppId(pkg.getUid());
- String pkgSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+ String pkgSeInfo = ps.getSeInfo();
Preconditions.checkNotNull(pkgSeInfo);
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
new file mode 100644
index 0000000..9ea350f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.Context;
+import android.media.IAudioService;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class to provide queries for app states concerning gentle-update.
+ */
+public class AppStateHelper {
+ private final Context mContext;
+
+ public AppStateHelper(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * True if the package is loaded into the process.
+ */
+ private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) {
+ return ArrayUtils.contains(info.pkgList, packageName)
+ || ArrayUtils.contains(info.pkgDeps, packageName);
+ }
+
+ /**
+ * Returns the importance of the given package.
+ */
+ private int getImportance(String packageName) {
+ var am = mContext.getSystemService(ActivityManager.class);
+ return am.getPackageImportance(packageName);
+ }
+
+ /**
+ * True if the app owns the audio focus.
+ */
+ private boolean hasAudioFocus(String packageName) {
+ var audioService = IAudioService.Stub.asInterface(
+ ServiceManager.getService(Context.AUDIO_SERVICE));
+ try {
+ var focusInfos = audioService.getFocusStack();
+ int size = focusInfos.size();
+ var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null;
+ return TextUtils.equals(packageName, audioFocusPackage);
+ } catch (Exception ignore) {
+ }
+ return false;
+ }
+
+ /**
+ * True if the app is in the foreground.
+ */
+ private boolean isAppForeground(String packageName) {
+ return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+ }
+
+ /**
+ * True if the app is currently at the top of the screen that the user is interacting with.
+ */
+ public boolean isAppTopVisible(String packageName) {
+ return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+ }
+
+ /**
+ * True if the app is playing/recording audio.
+ */
+ private boolean hasActiveAudio(String packageName) {
+ // TODO(b/235306967): also check recording
+ return hasAudioFocus(packageName);
+ }
+
+ /**
+ * True if the app is sending or receiving network data.
+ */
+ private boolean hasActiveNetwork(String packageName) {
+ // To be implemented
+ return false;
+ }
+
+ /**
+ * True if any app is interacting with the user.
+ */
+ public boolean hasInteractingApp(List<String> packageNames) {
+ for (var packageName : packageNames) {
+ if (hasActiveAudio(packageName)
+ || hasActiveNetwork(packageName)
+ || isAppTopVisible(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * True if any app is in the foreground.
+ */
+ public boolean hasForegroundApp(List<String> packageNames) {
+ for (var packageName : packageNames) {
+ if (isAppForeground(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * True if any app is top visible.
+ */
+ public boolean hasTopVisibleApp(List<String> packageNames) {
+ for (var packageName : packageNames) {
+ if (isAppTopVisible(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * True if there is an ongoing phone call.
+ */
+ public boolean isInCall() {
+ // To be implemented
+ return false;
+ }
+
+ /**
+ * Returns a list of packages which depend on {@code packageNames}. These are the packages
+ * that will be affected when updating {@code packageNames} and should participate in
+ * the evaluation of install constraints.
+ *
+ * TODO(b/235306967): Also include bounded services as dependency.
+ */
+ public List<String> getDependencyPackages(List<String> packageNames) {
+ var results = new ArraySet<String>();
+ var am = mContext.getSystemService(ActivityManager.class);
+ for (var info : am.getRunningAppProcesses()) {
+ for (var packageName : packageNames) {
+ if (!isPackageLoaded(info, packageName)) {
+ continue;
+ }
+ for (var pkg : info.pkgList) {
+ results.add(pkg);
+ }
+ }
+ }
+ return new ArrayList<>(results);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
new file mode 100644
index 0000000..247ac90
--- /dev/null
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.WorkerThread;
+import android.app.ActivityThread;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Slog;
+
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A helper class to coordinate install flow for sessions with install constraints.
+ * These sessions will be pending and wait until the constraints are satisfied to
+ * resume installation.
+ */
+public class GentleUpdateHelper {
+ private static final String TAG = "GentleUpdateHelper";
+ private static final int JOB_ID = 235306967; // bug id
+ // The timeout used to determine whether the device is idle or not.
+ private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10);
+
+ /**
+ * A wrapper class used by JobScheduler to schedule jobs.
+ */
+ public static class Service extends JobService {
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ try {
+ var pis = (PackageInstallerService) ActivityThread.getPackageManager()
+ .getPackageInstaller();
+ var helper = pis.getGentleUpdateHelper();
+ helper.mHandler.post(helper::runIdleJob);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get PackageInstallerService", e);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+ }
+
+ private static class PendingInstallConstraintsCheck {
+ public final List<String> packageNames;
+ public final InstallConstraints constraints;
+ public final CompletableFuture<InstallConstraintsResult> future;
+ PendingInstallConstraintsCheck(List<String> packageNames,
+ InstallConstraints constraints,
+ CompletableFuture<InstallConstraintsResult> future) {
+ this.packageNames = packageNames;
+ this.constraints = constraints;
+ this.future = future;
+ }
+ }
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final AppStateHelper mAppStateHelper;
+ // Worker thread only
+ private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
+ private boolean mHasPendingIdleJob;
+
+ GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
+ mContext = context;
+ mHandler = new Handler(looper);
+ mAppStateHelper = appStateHelper;
+ }
+
+ /**
+ * Checks if install constraints are satisfied for the given packages.
+ */
+ CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
+ List<String> packageNames, InstallConstraints constraints) {
+ var future = new CompletableFuture<InstallConstraintsResult>();
+ mHandler.post(() -> {
+ var pendingCheck = new PendingInstallConstraintsCheck(
+ packageNames, constraints, future);
+ if (constraints.isRequireDeviceIdle()) {
+ mPendingChecks.add(pendingCheck);
+ // JobScheduler doesn't provide queries about whether the device is idle.
+ // We schedule 2 tasks to determine device idle. If the idle job is executed
+ // before the delayed runnable, we know the device is idle.
+ // Note #processPendingCheck will be no-op for the task executed later.
+ scheduleIdleJob();
+ mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+ PENDING_CHECK_MILLIS);
+ } else {
+ processPendingCheck(pendingCheck, false);
+ }
+ });
+ return future;
+ }
+
+ @WorkerThread
+ private void scheduleIdleJob() {
+ if (mHasPendingIdleJob) {
+ // No need to schedule the job again
+ return;
+ }
+ mHasPendingIdleJob = true;
+ var componentName = new ComponentName(
+ mContext.getPackageName(), GentleUpdateHelper.Service.class.getName());
+ var jobInfo = new JobInfo.Builder(JOB_ID, componentName)
+ .setRequiresDeviceIdle(true)
+ .build();
+ var jobScheduler = mContext.getSystemService(JobScheduler.class);
+ jobScheduler.schedule(jobInfo);
+ }
+
+ @WorkerThread
+ private void runIdleJob() {
+ mHasPendingIdleJob = false;
+ processPendingChecksInIdle();
+ }
+
+ @WorkerThread
+ private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+ var future = pendingCheck.future;
+ if (future.isDone()) {
+ return;
+ }
+ var constraints = pendingCheck.constraints;
+ var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+ var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
+ && (!constraints.isRequireAppNotForeground()
+ || !mAppStateHelper.hasForegroundApp(packageNames))
+ && (!constraints.isRequireAppNotInteracting()
+ || !mAppStateHelper.hasInteractingApp(packageNames))
+ && (!constraints.isRequireAppNotTopVisible()
+ || !mAppStateHelper.hasTopVisibleApp(packageNames))
+ && (!constraints.isRequireNotInCall()
+ || !mAppStateHelper.isInCall());
+ future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+ }
+
+ @WorkerThread
+ private void processPendingChecksInIdle() {
+ while (!mPendingChecks.isEmpty()) {
+ processPendingCheck(mPendingChecks.remove(), true);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 78e4190..3c72019 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -765,7 +765,7 @@
|| (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
if (ps != null && doSnapshotOrRestore) {
- final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps);
+ final String seInfo = ps.getSeInfo();
final RollbackManagerInternal rollbackManager =
mInjector.getLocalService(RollbackManagerInternal.class);
rollbackManager.snapshotAndRestoreUserData(packageName,
@@ -2259,20 +2259,26 @@
incrementalStorages.add(storage);
}
- try {
- if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
- VerityUtils.setUpFsverity(pkg.getBaseApkPath(), (byte[]) null);
- }
- for (String path : pkg.getSplitCodePaths()) {
- if (!VerityUtils.hasFsverity(path)) {
- VerityUtils.setUpFsverity(path, (byte[]) null);
+ // Enabling fs-verity is a blocking operation. To reduce the impact to the install time,
+ // run in a background thread.
+ new Thread("fsverity-setup") {
+ @Override public void run() {
+ try {
+ if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
+ VerityUtils.setUpFsverity(pkg.getBaseApkPath(), (byte[]) null);
+ }
+ for (String path : pkg.getSplitCodePaths()) {
+ if (!VerityUtils.hasFsverity(path)) {
+ VerityUtils.setUpFsverity(path, (byte[]) null);
+ }
+ }
+ } catch (IOException e) {
+ // There's nothing we can do if the setup failed. Since fs-verity is
+ // optional, just ignore the error for now.
+ Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
}
}
- } catch (IOException e) {
- // There's nothing we can do if the setup failed. Since fs-verity is
- // optional, just ignore the error for now.
- Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
- }
+ }.start();
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index b27373e..b66c6ac 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -129,7 +129,7 @@
final InstallSource installSource = packageState.getInstallSource();
final String packageAbiOverride = packageState.getCpuAbiOverride();
final int appId = UserHandle.getAppId(pkg.getUid());
- final String seinfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+ final String seinfo = packageState.getSeInfo();
final String label = String.valueOf(pm.getApplicationLabel(
AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
final int targetSdkVersion = pkg.getTargetSdkVersion();
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 226a27e..49f3a3c 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -493,7 +493,7 @@
// TODO: Consider adding 2 different APIs for primary and secondary dexopt.
// installd only uses downgrade flag for secondary dex files and ignores it for
// primary dex files.
- String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+ String seInfo = pkgSetting.getSeInfo();
boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa,
dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(),
classLoaderContext, seInfo, /* downgrade= */ false ,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 653a882..409d352 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -44,6 +44,7 @@
import android.content.pm.IPackageInstallerSession;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallConstraints;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageItemInfo;
@@ -54,12 +55,14 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SELinux;
@@ -88,6 +91,7 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
@@ -186,6 +190,7 @@
private final InternalCallback mInternalCallback = new InternalCallback();
private final PackageSessionVerifier mSessionVerifier;
+ private final GentleUpdateHelper mGentleUpdateHelper;
/**
* Used for generating session IDs. Since this is created at boot time,
@@ -272,6 +277,8 @@
mStagingManager = new StagingManager(context);
mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
apexParserSupplier, mInstallThread.getLooper());
+ mGentleUpdateHelper = new GentleUpdateHelper(
+ context, mInstallThread.getLooper(), new AppStateHelper(context));
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -1233,6 +1240,33 @@
}
@Override
+ public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+ InstallConstraints constraints, RemoteCallback callback) {
+ Preconditions.checkArgument(packageNames != null);
+ Preconditions.checkArgument(constraints != null);
+ Preconditions.checkArgument(callback != null);
+
+ final var snapshot = mPm.snapshotComputer();
+ final int callingUid = Binder.getCallingUid();
+ if (!isCalledBySystemOrShell(callingUid)) {
+ for (var packageName : packageNames) {
+ var ps = snapshot.getPackageStateInternal(packageName);
+ if (ps == null || !TextUtils.equals(
+ ps.getInstallSource().mInstallerPackageName, installerPackageName)) {
+ throw new SecurityException("Caller has no access to package " + packageName);
+ }
+ }
+ }
+
+ var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+ future.thenAccept(result -> {
+ var b = new Bundle();
+ b.putParcelable("result", result);
+ callback.sendResult(b);
+ });
+ }
+
+ @Override
public void registerCallback(IPackageInstallerCallback callback, int userId) {
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
@@ -1265,6 +1299,11 @@
}
@Override
+ public GentleUpdateHelper getGentleUpdateHelper() {
+ return mGentleUpdateHelper;
+ }
+
+ @Override
public void bypassNextStagedInstallerCheck(boolean value) {
if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to bypass staged installer check");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f8cc8a..9e1bffb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1560,7 +1560,7 @@
AndroidPackage pkg = packageState.getPkg();
SharedUserApi sharedUser = snapshot.getSharedUser(
packageState.getSharedUserAppId());
- String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+ String oldSeInfo = packageState.getSeInfo();
if (pkg == null) {
Slog.e(TAG, "Failed to find package " + packageName);
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
index ad5cf13..79b88b3 100644
--- a/services/core/java/com/android/server/pm/PackageSessionProvider.java
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -29,4 +29,9 @@
PackageInstallerSession getSession(int sessionId);
PackageSessionVerifier getSessionVerifier();
+
+ /**
+ * Get the GentleUpdateHelper instance.
+ */
+ GentleUpdateHelper getGentleUpdateHelper();
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 6d90593..3ec6e7d 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1358,6 +1358,17 @@
}
@Nullable
+ @Override
+ public String getSeInfo() {
+ String overrideSeInfo = getTransientState().getOverrideSeInfo();
+ if (!TextUtils.isEmpty(overrideSeInfo)) {
+ return overrideSeInfo;
+ }
+
+ return getTransientState().getSeInfo();
+ }
+
+ @Nullable
public String getPrimaryCpuAbiLegacy() {
return mPrimaryCpuAbi;
}
@@ -1518,10 +1529,10 @@
}
@DataClass.Generated(
- time = 1662666062860L,
+ time = 1665779003744L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index a905df9..6572d7b 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -265,8 +265,8 @@
pkgSetting.getPkgState().setUpdatedSystemApp(true);
}
- parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
- injector.getCompatibility()));
+ pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage,
+ sharedUserSetting, injector.getCompatibility()));
if (parsedPackage.isSystem()) {
configurePackageComponents(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a40d404..4aba016 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -102,7 +102,6 @@
import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.LegacyPermissionSettings;
import com.android.server.pm.permission.LegacyPermissionState;
@@ -2900,7 +2899,7 @@
sb.append(isDebug ? " 1 " : " 0 ");
sb.append(dataPath);
sb.append(" ");
- sb.append(AndroidPackageUtils.getSeInfo(pkg.getPkg(), pkg));
+ sb.append(pkg.getSeInfo());
sb.append(" ");
final int gidsSize = gids.size();
if (gids != null && gids.size() > 0) {
@@ -4359,7 +4358,7 @@
// (CE storage is not ready yet; the CE data directories will be created later,
// when the user is "unlocked".) Accumulate all required args, and call the
// installer after the mPackages lock has been released.
- final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
+ final String seInfo = ps.getSeInfo();
final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty();
final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(
ps.getVolumeUuid(), ps.getPackageName(), userHandle,
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 1da442b..74594cc 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
@@ -347,7 +346,7 @@
// an update, and hence need to restore data for all installed users.
final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true);
- final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+ final String seInfo = ps.getSeInfo();
rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
appId, ceDataInode, seInfo, 0 /*token*/);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 1027f4c..df132a9 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -441,4 +441,12 @@
/** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
*/
public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
+
+ /**
+ * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is
+ * no main user.
+ *
+ * @see UserManager#isMainUser()
+ */
+ public abstract @UserIdInt int getMainUserId();
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 23a6b67..02a57b2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -901,6 +901,25 @@
return null;
}
+ @Override
+ public @UserIdInt int getMainUserId() {
+ checkQueryOrCreateUsersPermission("get main user id");
+ return getMainUserIdUnchecked();
+ }
+
+ private @UserIdInt int getMainUserIdUnchecked() {
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserInfo user = mUsers.valueAt(i).info;
+ if (user.isMain() && !mRemovingUserIds.get(user.id)) {
+ return user.id;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
true);
@@ -6898,6 +6917,12 @@
}
return userTypes;
}
+
+ @Override
+ public @UserIdInt int getMainUserId() {
+ return getMainUserIdUnchecked();
+ }
+
} // class LocalService
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 2650b23..878855a 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -575,9 +575,7 @@
ipw.println(mCurrentUserId);
ipw.print("Visible users: ");
- // TODO: merge 2 lines below if/when IntArray implements toString()...
- IntArray visibleUsers = getVisibleUsers();
- ipw.println(java.util.Arrays.toString(visibleUsers.toArray()));
+ ipw.println(getVisibleUsers());
dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
"u", "pg");
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a7d4cea..558202b 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -452,7 +452,7 @@
info.category = pkgSetting.getCategoryOverride();
}
- info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+ info.seInfo = pkgSetting.getSeInfo();
info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
index 944e4ad..876bf17 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
@@ -40,13 +40,6 @@
String getPrimaryCpuAbi();
/**
- * @see ApplicationInfo#seInfo
- * TODO: This field is deriveable and might not have to be cached here.
- */
- @Nullable
- String getSeInfo();
-
- /**
* @see ApplicationInfo#secondaryCpuAbi
*/
@Nullable
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 5b0cc51..c76b129 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -27,7 +27,6 @@
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.incremental.IncrementalManager;
-import android.text.TextUtils;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.util.ArrayUtils;
@@ -288,16 +287,6 @@
return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi();
}
- public static String getSeInfo(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
- if (pkgSetting != null) {
- String overrideSeInfo = pkgSetting.getTransientState().getOverrideSeInfo();
- if (!TextUtils.isEmpty(overrideSeInfo)) {
- return overrideSeInfo;
- }
- }
- return ((AndroidPackageHidden) pkg).getSeInfo();
- }
-
@Deprecated
@NonNull
public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) {
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index a43b979..b1e9141 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -462,10 +462,6 @@
@DataClass.ParcelWith(ForInternedString.class)
protected String secondaryNativeLibraryDir;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- protected String seInfo;
-
/**
* This is an appId, the uid if the userId is == USER_SYSTEM
*/
@@ -2905,12 +2901,6 @@
}
@Override
- public PackageImpl setSeInfo(@Nullable String seInfo) {
- this.seInfo = TextUtils.safeIntern(seInfo);
- return this;
- }
-
- @Override
public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) {
this.splitCodePaths = splitCodePaths;
if (splitCodePaths != null) {
@@ -2993,7 +2983,6 @@
appInfo.primaryCpuAbi = primaryCpuAbi;
appInfo.secondaryCpuAbi = secondaryCpuAbi;
appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
- appInfo.seInfo = seInfo;
appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR;
appInfo.uid = uid;
return appInfo;
@@ -3147,7 +3136,6 @@
sForInternedString.parcel(this.primaryCpuAbi, dest, flags);
sForInternedString.parcel(this.secondaryCpuAbi, dest, flags);
dest.writeString(this.secondaryNativeLibraryDir);
- dest.writeString(this.seInfo);
dest.writeInt(this.uid);
dest.writeLong(this.mBooleans);
dest.writeLong(this.mBooleans2);
@@ -3307,7 +3295,6 @@
this.primaryCpuAbi = sForInternedString.unparcel(in);
this.secondaryCpuAbi = sForInternedString.unparcel(in);
this.secondaryNativeLibraryDir = in.readString();
- this.seInfo = in.readString();
this.uid = in.readInt();
this.mBooleans = in.readLong();
this.mBooleans2 = in.readLong();
@@ -3377,12 +3364,6 @@
return secondaryNativeLibraryDir;
}
- @Nullable
- @Override
- public String getSeInfo() {
- return seInfo;
- }
-
@Override
public boolean isCoreApp() {
return getBoolean(Booleans.CORE_APP);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
index d306341..aeaff6d 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
@@ -103,8 +103,6 @@
ParsedPackage setRestrictUpdateHash(byte[] restrictUpdateHash);
- ParsedPackage setSeInfo(String seInfo);
-
ParsedPackage setSecondaryNativeLibraryDir(String secondaryNativeLibraryDir);
/**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3c79cdf..e8d0640 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -131,6 +131,14 @@
String getSecondaryCpuAbi();
/**
+ * @see ApplicationInfo#seInfo
+ * @return The SE info for this package, which may be overridden by a system configured value,
+ * or null if the package isn't available.
+ */
+ @Nullable
+ String getSeInfo();
+
+ /**
* @see AndroidPackage#isPrivileged()
*/
boolean isPrivileged();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index c6ce40e..e552a34 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -129,6 +129,8 @@
private final String mPrimaryCpuAbi;
@Nullable
private final String mSecondaryCpuAbi;
+ @Nullable
+ private final String mSeInfo;
private final boolean mHasSharedUser;
private final int mSharedUserAppId;
@NonNull
@@ -175,6 +177,7 @@
mPath = pkgState.getPath();
mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+ mSeInfo = pkgState.getSeInfo();
mHasSharedUser = pkgState.hasSharedUser();
mSharedUserAppId = pkgState.getSharedUserAppId();
mUsesSdkLibraries = pkgState.getUsesSdkLibraries();
@@ -542,7 +545,7 @@
}
@DataClass.Generated(
- time = 1661977809886L,
+ time = 1665778832625L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -641,6 +644,11 @@
}
@DataClass.Generated.Member
+ public @Nullable String getSeInfo() {
+ return mSeInfo;
+ }
+
+ @DataClass.Generated.Member
public boolean isHasSharedUser() {
return mHasSharedUser;
}
@@ -697,10 +705,10 @@
}
@DataClass.Generated(
- time = 1661977809932L,
+ time = 1665778832668L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index b22c038..57fbfe9 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
+import android.text.TextUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
@@ -62,6 +63,9 @@
@Nullable
private String overrideSeInfo;
+ @NonNull
+ private String seInfo;
+
// TODO: Remove in favor of finer grained change notification
@NonNull
private final PackageSetting mPackageSetting;
@@ -138,6 +142,7 @@
this.apkInUpdatedApex = other.apkInUpdatedApex;
this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
this.overrideSeInfo = other.overrideSeInfo;
+ this.seInfo = other.seInfo;
mPackageSetting.onChanged();
}
@@ -206,6 +211,13 @@
return this;
}
+ @NonNull
+ public PackageStateUnserialized setSeInfo(@NonNull String value) {
+ seInfo = TextUtils.safeIntern(value);
+ mPackageSetting.onChanged();
+ return this;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -271,15 +283,20 @@
}
@DataClass.Generated.Member
+ public @NonNull String getSeInfo() {
+ return seInfo;
+ }
+
+ @DataClass.Generated.Member
public @NonNull PackageSetting getPackageSetting() {
return mPackageSetting;
}
@DataClass.Generated(
- time = 1661373697219L,
+ time = 1666291743725L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
- inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+ inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3aa333a..e9c93ee 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4208,11 +4208,13 @@
wakeUpFromWakeKey(event);
}
- if ((result & ACTION_PASS_TO_USER) != 0) {
+ if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled
+ && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
// If the key event is targeted to a specific display, then the user is interacting with
- // that display. Therefore, give focus to the display that the user is interacting with.
- if (!mPerDisplayFocusEnabled
- && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+ // that display. Therefore, give focus to the display that the user is interacting with,
+ // unless that display maintains its own focus.
+ Display display = mDisplayManager.getDisplay(displayId);
+ if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) {
// An event is targeting a non-focused display. Move the display to top so that
// it can become the focused display to interact with the user.
// This should be done asynchronously, once the focus logic is fully moved to input
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9281f4b..1ea0988 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2204,6 +2204,15 @@
if (sQuiescent) {
mDirty |= DIRTY_QUIESCENT;
}
+ PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+ if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+ // Workaround for b/187231320 where the AOD can get stuck in a "half on /
+ // half off" state when a non-default-group VirtualDisplay causes the global
+ // wakefulness to change to awake, even though the default display is
+ // dozing. We set sandman summoned to restart dreaming to get it unstuck.
+ // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
+ defaultGroup.setSandmanSummonedLocked(true);
+ }
break;
case WAKEFULNESS_ASLEEP:
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index fbb6644..f17e5e7 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -43,6 +43,43 @@
public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);
/**
+ * Creates a sensor that is registered at runtime by the system with the sensor service.
+ *
+ * The runtime sensors created here are different from the
+ * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors">
+ * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to
+ * sensors that belong to an external (virtual) device.
+ *
+ * @param deviceId The identifier of the device this sensor is associated with.
+ * @param type The generic type of the sensor.
+ * @param name The name of the sensor.
+ * @param vendor The vendor string of the sensor.
+ * @param callback The callback to get notified when the sensor listeners have changed.
+ * @return The sensor handle.
+ */
+ public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+ @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);
+
+ /**
+ * Unregisters the sensor with the given handle from the framework.
+ */
+ public abstract void removeRuntimeSensor(int handle);
+
+ /**
+ * Sends an event for the runtime sensor with the given handle to the framework.
+ *
+ * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
+ *
+ * @param handle The sensor handle.
+ * @param type The type of the sensor.
+ * @param timestampNanos When the event occurred.
+ * @param values The values of the event.
+ * @return Whether the event injection was successful.
+ */
+ public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos,
+ @NonNull float[] values);
+
+ /**
* Listener for proximity sensor state changes.
*/
public interface ProximityActiveListener {
@@ -52,4 +89,17 @@
*/
void onProximityActive(boolean isActive);
}
+
+ /**
+ * Callback for runtime sensor state changes. Only relevant to sensors created via
+ * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
+ * not covered.
+ */
+ public interface RuntimeSensorStateChangeCallback {
+ /**
+ * Invoked when the listeners of the runtime sensor have changed.
+ */
+ void onStateChanged(boolean enabled, int samplingPeriodMicros,
+ int batchReportLatencyMicros);
+ }
}
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 8fe2d52..d8e3bdd 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -29,7 +29,9 @@
import com.android.server.SystemService;
import com.android.server.utils.TimingsTraceAndSlog;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -40,6 +42,8 @@
private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
new ArrayMap<>();
@GuardedBy("mLock")
+ private final Set<Integer> mRuntimeSensorHandles = new HashSet<>();
+ @GuardedBy("mLock")
private Future<?> mSensorServiceStart;
@GuardedBy("mLock")
private long mPtr;
@@ -51,6 +55,12 @@
private static native void registerProximityActiveListenerNative(long ptr);
private static native void unregisterProximityActiveListenerNative(long ptr);
+ private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
+ String name, String vendor,
+ SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
+ private static native void unregisterRuntimeSensorNative(long ptr, int handle);
+ private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
+ long timestampNanos, float[] values);
public SensorService(Context ctx) {
super(ctx);
@@ -85,6 +95,38 @@
class LocalService extends SensorManagerInternal {
@Override
+ public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+ @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
+ synchronized (mLock) {
+ int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+ callback);
+ mRuntimeSensorHandles.add(handle);
+ return handle;
+ }
+ }
+
+ @Override
+ public void removeRuntimeSensor(int handle) {
+ synchronized (mLock) {
+ if (mRuntimeSensorHandles.contains(handle)) {
+ mRuntimeSensorHandles.remove(handle);
+ unregisterRuntimeSensorNative(mPtr, handle);
+ }
+ }
+ }
+
+ @Override
+ public boolean sendSensorEvent(int handle, int type, long timestampNanos,
+ @NonNull float[] values) {
+ synchronized (mLock) {
+ if (!mRuntimeSensorHandles.contains(handle)) {
+ return false;
+ }
+ return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values);
+ }
+ }
+
+ @Override
public void addProximityActiveListener(@NonNull Executor executor,
@NonNull ProximityActiveListener listener) {
Objects.requireNonNull(executor, "executor must not be null");
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 0b1f6b9..f971db9 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -107,6 +107,7 @@
// Trust state
private boolean mTrusted;
private boolean mWaitingForTrustableDowngrade = false;
+ private boolean mWithinSecurityLockdownWindow = false;
private boolean mTrustable;
private CharSequence mMessage;
private boolean mDisplayTrustGrantedMessage;
@@ -160,6 +161,7 @@
mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
mWaitingForTrustableDowngrade = true;
+ setSecurityWindowTimer();
} else {
mWaitingForTrustableDowngrade = false;
}
@@ -452,6 +454,9 @@
if (mBound) {
scheduleRestart();
}
+ if (mWithinSecurityLockdownWindow) {
+ mTrustManagerService.lockUser(mUserId);
+ }
// mTrustDisabledByDpm maintains state
}
};
@@ -673,6 +678,22 @@
}
}
+ private void setSecurityWindowTimer() {
+ mWithinSecurityLockdownWindow = true;
+ long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds
+ mAlarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ expiration,
+ TAG,
+ new AlarmManager.OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ mWithinSecurityLockdownWindow = false;
+ }
+ },
+ Handler.getMain());
+ }
+
public boolean isManagingTrust() {
return mManagingTrust && !mTrustDisabledByDpm;
}
@@ -691,7 +712,6 @@
public void destroy() {
mHandler.removeMessages(MSG_RESTART_TIMEOUT);
-
if (!mBound) {
return;
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 14d6d7b..798e739 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -241,6 +241,7 @@
// We have another Activity in the same currentTask to go to
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
removedWindowContainer = currentActivity;
+ prevTask = prevActivity.getTask();
} else if (currentTask.returnsToHomeRootTask()) {
// Our Task should bring back to home
removedWindowContainer = currentTask;
@@ -608,26 +609,23 @@
// reset leash after animation finished.
leashes.add(screenshotSurface);
}
- } else if (prevTask != null) {
- prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevActivity != null) {
- // Make previous task show from behind by marking its top activity as visible
- // and launch-behind to bump its visibility for the duration of the back gesture.
- setLaunchBehind(prevActivity);
+ } else if (prevTask != null && prevActivity != null) {
+ // Make previous task show from behind by marking its top activity as visible
+ // and launch-behind to bump its visibility for the duration of the back gesture.
+ setLaunchBehind(prevActivity);
- final SurfaceControl leash = prevActivity.makeAnimationLeash()
- .setName("BackPreview Leash for " + prevActivity)
- .setHidden(false)
- .build();
- prevActivity.reparentSurfaceControl(startedTransaction, leash);
- behindAppTarget = createRemoteAnimationTargetLocked(
- prevTask, leash, MODE_OPENING);
+ final SurfaceControl leash = prevActivity.makeAnimationLeash()
+ .setName("BackPreview Leash for " + prevActivity)
+ .setHidden(false)
+ .build();
+ prevActivity.reparentSurfaceControl(startedTransaction, leash);
+ behindAppTarget = createRemoteAnimationTargetLocked(
+ prevTask, leash, MODE_OPENING);
- // reset leash after animation finished.
- leashes.add(leash);
- prevActivity.reparentSurfaceControl(finishedTransaction,
- prevActivity.getParentSurfaceControl());
- }
+ // reset leash after animation finished.
+ leashes.add(leash);
+ prevActivity.reparentSurfaceControl(finishedTransaction,
+ prevActivity.getParentSurfaceControl());
}
if (mShowWallpaper) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0119e4d..9c920f53 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3380,7 +3380,7 @@
}
}
mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
- controller.mTransitionMetricsReporter.associate(t,
+ controller.mTransitionMetricsReporter.associate(t.getToken(),
startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
startAsyncRotation(false /* shouldDebounce */);
}
@@ -3683,7 +3683,7 @@
* @return The focused window or null if there isn't any or no need to seek.
*/
WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
- return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
+ return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)
? findFocusedWindow() : null;
}
@@ -6315,6 +6315,14 @@
}
/**
+ * @return whether this display maintains its own focus and touch mode.
+ */
+ boolean hasOwnFocus() {
+ return mWmService.mPerDisplayFocusEnabled
+ || (mDisplayInfo.flags & Display.FLAG_OWN_FOCUS) != 0;
+ }
+
+ /**
* @return whether the keyguard is occluded on this display
*/
boolean isKeyguardOccluded() {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 7860b15..3e1105b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -270,7 +270,7 @@
InputConfigAdapter.getMask());
final boolean focusable = w.canReceiveKeys()
- && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
+ && (mDisplayContent.hasOwnFocus() || mDisplayContent.isOnTop());
inputWindowHandle.setFocusable(focusable);
final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 2dbccae..bb4c482 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -87,10 +87,6 @@
private final LetterboxConfiguration mLetterboxConfiguration;
private final ActivityRecord mActivityRecord;
- // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
- // corners above the taskbar.
- private final float mExpandedTaskBarHeight;
-
private boolean mShowWallpaperForLetterboxBackground;
@Nullable
@@ -102,8 +98,6 @@
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
- mExpandedTaskBarHeight =
- getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -285,14 +279,17 @@
}
float getSplitScreenAspectRatio() {
+ // Getting the same aspect ratio that apps get in split screen.
+ final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+ if (displayContent == null) {
+ return getDefaultMinAspectRatioForUnresizableApps();
+ }
int dividerWindowWidth =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
int dividerInsets =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
int dividerSize = dividerWindowWidth - dividerInsets * 2;
-
- // Getting the same aspect ratio that apps get in split screen.
- Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+ final Rect bounds = new Rect(displayContent.getBounds());
if (bounds.width() >= bounds.height()) {
bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
bounds.right = bounds.centerX();
@@ -555,7 +552,6 @@
final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
return taskbarInsetsSource != null
- && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
&& taskbarInsetsSource.isVisible();
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b277804..9cb13e4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -91,6 +91,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -100,7 +101,7 @@
* Represents a logical transition.
* @see TransitionController
*/
-class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+class Transition implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "Transition";
private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
@@ -151,6 +152,7 @@
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
+ private final Token mToken;
private RemoteTransition mRemoteTransition = null;
/** Only use for clean-up after binder death! */
@@ -213,10 +215,26 @@
mFlags = flags;
mController = controller;
mSyncEngine = syncEngine;
+ mToken = new Token(this);
controller.mTransitionTracer.logState(this);
}
+ @Nullable
+ static Transition fromBinder(@NonNull IBinder token) {
+ try {
+ return ((Token) token).mTransition.get();
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Invalid transition token: " + token, e);
+ return null;
+ }
+ }
+
+ @NonNull
+ IBinder getToken() {
+ return mToken;
+ }
+
void addFlag(int flag) {
mFlags |= flag;
}
@@ -726,6 +744,11 @@
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
}
+ // Close the transactions now. They were originally copied to Shell in case we needed to
+ // apply them due to a remote failure. Since we don't need to apply them anymore, free them
+ // immediately.
+ if (mStartTransaction != null) mStartTransaction.close();
+ if (mFinishTransaction != null) mFinishTransaction.close();
mStartTransaction = mFinishTransaction = null;
if (mState < STATE_PLAYING) {
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
@@ -867,6 +890,7 @@
mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
+ cleanUpInternal();
}
void abort() {
@@ -909,6 +933,7 @@
dc.getPendingTransaction().merge(transaction);
mSyncId = -1;
mOverrideOptions = null;
+ cleanUpInternal();
return;
}
@@ -1026,7 +1051,9 @@
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
mController.getTransitionPlayer().onTransitionReady(
- this, info, transaction, mFinishTransaction);
+ mToken, info, transaction, mFinishTransaction);
+ // Since we created root-leash but no longer reference it from core, release it now
+ info.releaseAnimSurfaces();
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
@@ -1059,7 +1086,17 @@
if (mFinishTransaction != null) {
mFinishTransaction.apply();
}
- mController.finishTransition(this);
+ mController.finishTransition(mToken);
+ }
+
+ private void cleanUpInternal() {
+ // Clean-up any native references.
+ for (int i = 0; i < mChanges.size(); ++i) {
+ final ChangeInfo ci = mChanges.valueAt(i);
+ if (ci.mSnapshot != null) {
+ ci.mSnapshot.release();
+ }
+ }
}
/** @see RecentsAnimationController#attachNavigationBarToApp */
@@ -1815,10 +1852,6 @@
return isCollecting() && mSyncId >= 0;
}
- static Transition fromBinder(IBinder binder) {
- return (Transition) binder;
- }
-
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
@@ -2325,4 +2358,18 @@
}
}
}
+
+ private static class Token extends Binder {
+ final WeakReference<Transition> mTransition;
+
+ Token(Transition transition) {
+ mTransition = new WeakReference<>(transition);
+ }
+
+ @Override
+ public String toString() {
+ return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+ + mTransition.get() + "}";
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 25df511..99527b1 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -458,8 +458,9 @@
info = new ActivityManager.RunningTaskInfo();
startTask.fillTaskInfo(info);
}
- mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
- transition.mType, info, remoteTransition, displayChange));
+ mTransitionPlayer.requestStartTransition(transition.getToken(),
+ new TransitionRequestInfo(transition.mType, info, remoteTransition,
+ displayChange));
transition.setRemoteTransition(remoteTransition);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6032f87..f6f825f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3847,6 +3847,11 @@
|| displayContent.isInTouchMode() == inTouch)) {
return;
}
+ final boolean displayHasOwnTouchMode =
+ displayContent != null && displayContent.hasOwnFocus();
+ if (displayHasOwnTouchMode && displayContent.isInTouchMode() == inTouch) {
+ return;
+ }
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final boolean hasPermission =
@@ -3855,17 +3860,17 @@
/* printlog= */ false);
final long token = Binder.clearCallingIdentity();
try {
- // If perDisplayFocusEnabled is set, then just update the display pointed by
- // displayId
- if (perDisplayFocusEnabled) {
+ // If perDisplayFocusEnabled is set or the display maintains its own touch mode,
+ // then just update the display pointed by displayId
+ if (perDisplayFocusEnabled || displayHasOwnTouchMode) {
if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
displayContent.setInTouchMode(inTouch);
}
- } else { // Otherwise update all displays
+ } else { // Otherwise update all displays that do not maintain their own touch mode
final int displayCount = mRoot.mChildren.size();
for (int i = 0; i < displayCount; ++i) {
DisplayContent dc = mRoot.mChildren.get(i);
- if (dc.isInTouchMode() == inTouch) {
+ if (dc.isInTouchMode() == inTouch || dc.hasOwnFocus()) {
continue;
}
if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
@@ -8935,14 +8940,14 @@
}
@Override
- public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+ public List<DisplayInfo> getPossibleDisplayInfo(int displayId) {
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
- Slog.e(TAG, "Unable to verify uid for package " + packageName
- + " for getPossibleMaximumWindowMetrics");
+ if (!mAtmService.isCallerRecents(callingUid)) {
+ Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+ + " on uid " + callingUid);
return new ArrayList<>();
}
@@ -8960,31 +8965,6 @@
return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
}
- /**
- * Returns {@code true} when the calling package is the recents component.
- */
- boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
- String recentsPackage;
- try {
- String recentsComponent = mContext.getResources().getString(
- R.string.config_recentsComponentName);
- if (recentsComponent == null) {
- return false;
- }
- recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- try {
- return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
- && callingPackageName.equals(recentsPackage);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- }
-
void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
synchronized (mGlobalLock) {
final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4c35178..aa1cf56 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -306,7 +306,7 @@
nextTransition.setAllReady();
}
});
- return nextTransition;
+ return nextTransition.getToken();
}
transition = mTransitionController.createTransition(type);
}
@@ -315,7 +315,7 @@
if (needsSetReady) {
transition.setAllReady();
}
- return transition;
+ return transition.getToken();
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 63b7dfb..10d8b42 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -22,6 +22,7 @@
#include <cutils/properties.h>
#include <jni.h>
#include <sensorservice/SensorService.h>
+#include <string.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -30,10 +31,14 @@
#define PROXIMITY_ACTIVE_CLASS \
"com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
+#define RUNTIME_SENSOR_CALLBACK_CLASS \
+ "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback"
+
namespace android {
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnProximityActive;
+static jmethodID sMethodIdOnStateChanged;
class NativeSensorService {
public:
@@ -41,6 +46,11 @@
void registerProximityActiveListener();
void unregisterProximityActiveListener();
+ jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
+ jobject callback);
+ void unregisterRuntimeSensor(jint handle);
+ jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
+ jfloatArray values);
private:
sp<SensorService> mService;
@@ -56,6 +66,18 @@
jobject mListener;
};
sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
+
+ class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback {
+ public:
+ RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback);
+ ~RuntimeSensorCallbackDelegate();
+
+ void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) override;
+
+ private:
+ jobject mCallback;
+ };
};
NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener)
@@ -85,6 +107,109 @@
mService->removeProximityActiveListener(mProximityActiveListenerDelegate);
}
+jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
+ jstring vendor, jobject callback) {
+ if (mService == nullptr) {
+ ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
+ return -1;
+ }
+
+ sensor_t sensor{
+ .name = env->GetStringUTFChars(name, 0),
+ .vendor = env->GetStringUTFChars(vendor, 0),
+ .version = sizeof(sensor_t),
+ .type = type,
+ };
+
+ sp<RuntimeSensorCallbackDelegate> callbackDelegate(
+ new RuntimeSensorCallbackDelegate(env, callback));
+ return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate);
+}
+
+void NativeSensorService::unregisterRuntimeSensor(jint handle) {
+ if (mService == nullptr) {
+ ALOGD("Dropping unregisterProximityActiveListener, sensor service not available.");
+ return;
+ }
+
+ mService->unregisterRuntimeSensor(handle);
+}
+
+jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type,
+ jlong timestamp, jfloatArray values) {
+ if (mService == nullptr) {
+ ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available.");
+ return false;
+ }
+ if (values == nullptr) {
+ ALOGD("Dropping sendRuntimeSensorEvent, no values.");
+ return false;
+ }
+
+ sensors_event_t event{
+ .version = sizeof(sensors_event_t),
+ .timestamp = timestamp,
+ .sensor = handle,
+ .type = type,
+ };
+
+ int valuesLength = env->GetArrayLength(values);
+ jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr);
+
+ switch (type) {
+ case SENSOR_TYPE_ACCELEROMETER:
+ case SENSOR_TYPE_MAGNETIC_FIELD:
+ case SENSOR_TYPE_ORIENTATION:
+ case SENSOR_TYPE_GYROSCOPE:
+ case SENSOR_TYPE_GRAVITY:
+ case SENSOR_TYPE_LINEAR_ACCELERATION: {
+ if (valuesLength != 3) {
+ ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+ return false;
+ }
+ event.acceleration.x = sensorValues[0];
+ event.acceleration.y = sensorValues[1];
+ event.acceleration.z = sensorValues[2];
+ break;
+ }
+ case SENSOR_TYPE_DEVICE_ORIENTATION:
+ case SENSOR_TYPE_LIGHT:
+ case SENSOR_TYPE_PRESSURE:
+ case SENSOR_TYPE_TEMPERATURE:
+ case SENSOR_TYPE_PROXIMITY:
+ case SENSOR_TYPE_RELATIVE_HUMIDITY:
+ case SENSOR_TYPE_AMBIENT_TEMPERATURE:
+ case SENSOR_TYPE_SIGNIFICANT_MOTION:
+ case SENSOR_TYPE_STEP_DETECTOR:
+ case SENSOR_TYPE_TILT_DETECTOR:
+ case SENSOR_TYPE_WAKE_GESTURE:
+ case SENSOR_TYPE_GLANCE_GESTURE:
+ case SENSOR_TYPE_PICK_UP_GESTURE:
+ case SENSOR_TYPE_WRIST_TILT_GESTURE:
+ case SENSOR_TYPE_STATIONARY_DETECT:
+ case SENSOR_TYPE_MOTION_DETECT:
+ case SENSOR_TYPE_HEART_BEAT:
+ case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: {
+ if (valuesLength != 1) {
+ ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+ return false;
+ }
+ event.data[0] = sensorValues[0];
+ break;
+ }
+ default: {
+ if (valuesLength > 16) {
+ ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum.");
+ return false;
+ }
+ memcpy(event.data, sensorValues, valuesLength * sizeof(float));
+ }
+ }
+
+ status_t err = mService->sendRuntimeSensorEvent(event);
+ return err == OK;
+}
+
NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
JNIEnv* env, jobject listener)
: mListener(env->NewGlobalRef(listener)) {}
@@ -98,6 +223,22 @@
jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive));
}
+NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env,
+ jobject callback)
+ : mCallback(env->NewGlobalRef(callback)) {}
+
+NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() {
+ AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged(
+ bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled),
+ static_cast<jint>(ns2us(samplingPeriodNs)),
+ static_cast<jint>(ns2us(batchReportLatencyNs)));
+}
+
static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
NativeSensorService* service = new NativeSensorService(env, listener);
return reinterpret_cast<jlong>(service);
@@ -113,26 +254,46 @@
service->unregisterProximityActiveListener();
}
-static const JNINativeMethod methods[] = {
- {
- "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
- reinterpret_cast<void*>(startSensorServiceNative)
- },
- {
- "registerProximityActiveListenerNative", "(J)V",
- reinterpret_cast<void*>(registerProximityActiveListenerNative)
- },
- {
- "unregisterProximityActiveListenerNative", "(J)V",
- reinterpret_cast<void*>(unregisterProximityActiveListenerNative)
- },
+static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
+ jstring name, jstring vendor, jobject callback) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+}
+static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ service->unregisterRuntimeSensor(handle);
+}
+
+static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type,
+ jlong timestamp, jfloatArray values) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values);
+}
+
+static const JNINativeMethod methods[] = {
+ {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
+ reinterpret_cast<void*>(startSensorServiceNative)},
+ {"registerProximityActiveListenerNative", "(J)V",
+ reinterpret_cast<void*>(registerProximityActiveListenerNative)},
+ {"unregisterProximityActiveListenerNative", "(J)V",
+ reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
+ {"registerRuntimeSensorNative",
+ "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+ reinterpret_cast<void*>(registerRuntimeSensorNative)},
+ {"unregisterRuntimeSensorNative", "(JI)V",
+ reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
+ {"sendRuntimeSensorEventNative", "(JIIJ[F)Z",
+ reinterpret_cast<void*>(sendRuntimeSensorEventNative)},
};
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) {
sJvm = vm;
jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
+ jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
+ sMethodIdOnStateChanged =
+ GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V");
return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
NELEM(methods));
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 8a8485a..9cb7533 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -16,24 +16,35 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Objects;
final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
@Override
- void saveToXml(TypedXmlSerializer serializer, String attributeName, Boolean value)
+ void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value)
throws IOException {
+ Objects.requireNonNull(value);
serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
}
+ @Nullable
@Override
- Boolean readFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException {
- return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+ Boolean readFromXml(TypedXmlPullParser parser, String attributeName) {
+ try {
+ return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+ } catch (XmlPullParserException e) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+ return null;
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 61d93c7..775e3d8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -81,6 +81,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT;
@@ -140,6 +141,7 @@
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -309,6 +311,7 @@
import android.provider.CalendarContract;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Telephony;
@@ -712,6 +715,17 @@
+ "management app's authentication policy";
private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
+ private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+ private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+
+ /**
+ * For apps targeting U+
+ * Enable multiple admins to coexist on the same device.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ static final long ENABLE_COEXISTENCE_CHANGE = 260560985L;
+
final Context mContext;
final Injector mInjector;
final PolicyPathProvider mPathProvider;
@@ -795,6 +809,8 @@
private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider;
private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver;
+ private final DevicePolicyEngine mDevicePolicyEngine;
+
private static final boolean ENABLE_LOCK_GUARD = true;
/**
@@ -1864,6 +1880,8 @@
mUserData = new SparseArray<>();
mOwners = makeOwners(injector, pathProvider);
+ mDevicePolicyEngine = new DevicePolicyEngine(mContext);
+
if (!mHasFeature) {
// Skip the rest of the initialization
mSetupContentObserver = null;
@@ -1908,6 +1926,9 @@
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
mDeviceManagementResourcesProvider.load();
+ if (isCoexistenceFlagEnabled()) {
+ mDevicePolicyEngine.load();
+ }
// The binder caches are not enabled until the first invalidation.
invalidateBinderCaches();
@@ -7951,8 +7972,17 @@
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+ if (isCoexistenceEnabled(caller)) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.AUTO_TIMEZONE,
+ // TODO(b/260573124): add correct enforcing admin when permission changes are
+ // merged.
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ enabled);
+ } else {
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+ }
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
@@ -12245,8 +12275,38 @@
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
- final int userHandle = caller.getUserId();
- setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+ }
+
+ if (isCoexistenceEnabled(caller)) {
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ if (packages.length == 0) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ admin,
+ caller.getUserId());
+ } else {
+ LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+ LockTaskPolicy policy;
+ if (currentPolicy == null) {
+ policy = new LockTaskPolicy(Set.of(packages));
+ } else {
+ policy = currentPolicy.clone();
+ policy.setPackages(Set.of(packages));
+ }
+
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ policy,
+ caller.getUserId());
+ }
+ } else {
+ synchronized (getLockObject()) {
+ final int userHandle = caller.getUserId();
+ setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+ }
}
}
@@ -12267,8 +12327,21 @@
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
- final List<String> packages = getUserData(userHandle).mLockTaskPackages;
- return packages.toArray(new String[packages.size()]);
+ }
+
+ if (isCoexistenceEnabled(caller)) {
+ LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+ if (policy == null) {
+ return new String[0];
+ } else {
+ return policy.getPackages().toArray(new String[policy.getPackages().size()]);
+ }
+ } else {
+ synchronized (getLockObject()) {
+ final List<String> packages = getUserData(userHandle).mLockTaskPackages;
+ return packages.toArray(new String[packages.size()]);
+ }
}
}
@@ -12284,8 +12357,19 @@
}
final int userId = mInjector.userHandleGetCallingUserId();
- synchronized (getLockObject()) {
- return getUserData(userId).mLockTaskPackages.contains(pkg);
+ // TODO(b/260560985): This is not the right check, as the flag could be enabled but there
+ // could be an admin that hasn't targeted U.
+ if (isCoexistenceFlagEnabled()) {
+ LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy();
+ if (policy == null) {
+ return false;
+ }
+ return policy.getPackages().contains(pkg);
+ } else {
+ synchronized (getLockObject()) {
+ return getUserData(userId).mLockTaskPackages.contains(pkg);
+ }
}
}
@@ -12308,7 +12392,28 @@
enforceCanCallLockTaskLocked(caller);
enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
- setLockTaskFeaturesLocked(userHandle, flags);
+ }
+ if (isCoexistenceEnabled(caller)) {
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+ if (currentPolicy == null) {
+ throw new IllegalArgumentException("Can't set a lock task flags without setting "
+ + "lock task packages first.");
+ }
+ LockTaskPolicy policy = currentPolicy.clone();
+ policy.setFlags(flags);
+
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ policy,
+ caller.getUserId());
+ } else {
+ synchronized (getLockObject()) {
+ setLockTaskFeaturesLocked(userHandle, flags);
+ }
}
}
@@ -12326,7 +12431,21 @@
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
- return getUserData(userHandle).mLockTaskFeatures;
+ }
+
+ if (isCoexistenceEnabled(caller)) {
+ LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+ if (policy == null) {
+ // We default on the power button menu, in order to be consistent with pre-P
+ // behaviour.
+ return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+ }
+ return policy.getFlags();
+ } else {
+ synchronized (getLockObject()) {
+ return getUserData(userHandle).mLockTaskFeatures;
+ }
}
}
@@ -13905,6 +14024,20 @@
if (isFinancedDeviceOwner(caller)) {
enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
}
+ }
+ if (isCoexistenceEnabled(caller)) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+ // TODO(b/260573124): Add correct enforcing admin when permission changes are
+ // merged, and don't forget to handle delegates! Enterprise admins assume
+ // component name isn't null.
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ grantState,
+ caller.getUserId());
+ // TODO: update javadoc to reflect that callback no longer return success/failure
+ callback.sendResult(Bundle.EMPTY);
+ } else {
+ synchronized (getLockObject()) {
long ident = mInjector.binderClearCallingIdentity();
try {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13921,14 +14054,16 @@
callback.sendResult(null);
return;
}
- if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+ if (grantState == PERMISSION_GRANT_STATE_GRANTED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
AdminPermissionControlParams permissionParams =
- new AdminPermissionControlParams(packageName, permission, grantState,
+ new AdminPermissionControlParams(packageName, permission,
+ grantState,
canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
mInjector.getPermissionControllerManager(caller.getUserHandle())
- .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
+ .setRuntimePermissionGrantStateByDeviceAdmin(
+ caller.getPackageName(),
permissionParams, mContext.getMainExecutor(),
(permissionWasSet) -> {
if (isPostQAdmin && !permissionWasSet) {
@@ -13947,13 +14082,14 @@
callback.sendResult(Bundle.EMPTY);
});
- }
- } catch (SecurityException e) {
- Slogf.e(LOG_TAG, "Could not set permission grant state", e);
+ }
+ } catch (SecurityException e) {
+ Slogf.e(LOG_TAG, "Could not set permission grant state", e);
- callback.sendResult(null);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
+ callback.sendResult(null);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
}
}
@@ -19017,4 +19153,18 @@
return result;
});
}
+
+ // TODO(b/260560985): properly gate coexistence changes
+ private boolean isCoexistenceEnabled(CallerIdentity caller) {
+ return isCoexistenceFlagEnabled()
+ && mInjector.isChangeEnabled(
+ ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId());
+ }
+
+ private boolean isCoexistenceFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ ENABLE_COEXISTENCE_FLAG,
+ DEFAULT_ENABLE_COEXISTENCE_FLAG);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index 3152f0b..d5949dd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -16,24 +16,35 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Objects;
final class IntegerPolicySerializer extends PolicySerializer<Integer> {
@Override
- void saveToXml(TypedXmlSerializer serializer, String attributeName, Integer value)
+ void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value)
throws IOException {
+ Objects.requireNonNull(value);
serializer.attributeInt(/* namespace= */ null, attributeName, value);
}
+ @Nullable
@Override
- Integer readFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException {
- return parser.getAttributeInt(/* namespace= */ null, attributeName);
+ Integer readFromXml(TypedXmlPullParser parser, String attributeName) {
+ try {
+ return parser.getAttributeInt(/* namespace= */ null, attributeName);
+ } catch (XmlPullParserException e) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+ return null;
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
index 9360fd7..d3e8de4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
@@ -16,7 +16,10 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.util.Log;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -24,19 +27,16 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
final class LockTaskPolicy {
- private Set<String> mPackages;
- private int mFlags;
+ static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+ private Set<String> mPackages = new HashSet<>();
+ private int mFlags = DEFAULT_LOCK_TASK_FLAG;
- LockTaskPolicy(@Nullable Set<String> packages, int flags) {
- mPackages = packages;
- mFlags = flags;
- }
-
- @Nullable
+ @NonNull
Set<String> getPackages() {
return mPackages;
}
@@ -45,8 +45,20 @@
return mFlags;
}
- void setPackages(Set<String> packages) {
- mPackages = packages;
+ LockTaskPolicy(Set<String> packages) {
+ Objects.requireNonNull(packages);
+ mPackages.addAll(packages);
+ }
+
+ private LockTaskPolicy(Set<String> packages, int flags) {
+ Objects.requireNonNull(packages);
+ mPackages = new HashSet<>(packages);
+ mFlags = flags;
+ }
+
+ void setPackages(@NonNull Set<String> packages) {
+ Objects.requireNonNull(packages);
+ mPackages = new HashSet<>(packages);
}
void setFlags(int flags) {
@@ -54,6 +66,13 @@
}
@Override
+ public LockTaskPolicy clone() {
+ LockTaskPolicy policy = new LockTaskPolicy(mPackages);
+ policy.setFlags(mFlags);
+ return policy;
+ }
+
+ @Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -67,6 +86,11 @@
return Objects.hash(mPackages, mFlags);
}
+ @Override
+ public String toString() {
+ return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags;
+ }
+
static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
private static final String ATTR_PACKAGES = ":packages";
@@ -74,15 +98,17 @@
private static final String ATTR_FLAGS = ":flags";
@Override
- void saveToXml(
- TypedXmlSerializer serializer, String attributeNamePrefix, LockTaskPolicy value)
- throws IOException {
- if (value.mPackages != null) {
- serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGES,
- String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
+ void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix,
+ @NonNull LockTaskPolicy value) throws IOException {
+ Objects.requireNonNull(value);
+ if (value.mPackages == null || value.mPackages.isEmpty()) {
+ throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
+ + "packages must be present");
}
+ serializer.attribute(
+ /* namespace= */ null,
+ attributeNamePrefix + ATTR_PACKAGES,
+ String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
serializer.attributeInt(
/* namespace= */ null,
attributeNamePrefix + ATTR_FLAGS,
@@ -90,18 +116,24 @@
}
@Override
- LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix)
- throws XmlPullParserException {
+ LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
String packagesStr = parser.getAttributeValue(
/* namespace= */ null,
attributeNamePrefix + ATTR_PACKAGES);
- Set<String> packages = packagesStr == null
- ? null
- : Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
- int flags = parser.getAttributeInt(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_FLAGS);
- return new LockTaskPolicy(packages, flags);
+ if (packagesStr == null) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+ return null;
+ }
+ Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
+ try {
+ int flags = parser.getAttributeInt(
+ /* namespace= */ null,
+ attributeNamePrefix + ATTR_FLAGS);
+ return new LockTaskPolicy(packages, flags);
+ } catch (XmlPullParserException e) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+ return null;
+ }
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 3a18cb9..a787a0b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -25,8 +25,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
@@ -225,8 +223,8 @@
mPolicySerializer.saveToXml(serializer, attributeName, value);
}
- V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException {
+ @Nullable
+ V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
return mPolicySerializer.readFromXml(parser, attributeName);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index b645b97..74b6f9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -29,6 +29,7 @@
import com.android.server.utils.Slogf;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -53,7 +54,7 @@
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull String[] args) {
- Binder.withCleanCallingIdentity(() -> {
+ return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (args == null || args.length < 2) {
throw new IllegalArgumentException("Package name and permission name must be "
+ "provided as arguments");
@@ -84,8 +85,7 @@
// TODO: add logging
return false;
}
- });
- return true;
+ }));
}
@NonNull
@@ -106,9 +106,14 @@
static boolean setLockTask(
@Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
- DevicePolicyManagerService.updateLockTaskPackagesLocked(
- context, List.copyOf(policy.getPackages()), userId);
- DevicePolicyManagerService.updateLockTaskFeaturesLocked(policy.getFlags(), userId);
+ List<String> packages = Collections.emptyList();
+ int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
+ if (policy != null) {
+ packages = List.copyOf(policy.getPackages());
+ flags = policy.getFlags();
+ }
+ DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
+ DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
return true;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index b3259d3..528d3b0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -16,16 +16,15 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
+
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
abstract class PolicySerializer<V> {
- abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, V value)
+ abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value)
throws IOException;
- abstract V readFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException;
+ abstract V readFromXml(TypedXmlPullParser parser, String attributeName);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 5fc3cb0..d3dee98 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -40,7 +40,7 @@
private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
private final PolicyDefinition<V> mPolicyDefinition;
- private final LinkedHashMap<EnforcingAdmin, V> mAdminsPolicy = new LinkedHashMap<>();
+ private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>();
private V mCurrentResolvedPolicy;
PolicyState(@NonNull PolicyDefinition<V> policyDefinition) {
@@ -49,13 +49,13 @@
private PolicyState(
@NonNull PolicyDefinition<V> policyDefinition,
- @NonNull LinkedHashMap<EnforcingAdmin, V> adminsPolicy,
+ @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins,
V currentEnforcedPolicy) {
Objects.requireNonNull(policyDefinition);
- Objects.requireNonNull(adminsPolicy);
+ Objects.requireNonNull(policiesSetByAdmins);
mPolicyDefinition = policyDefinition;
- mAdminsPolicy.putAll(adminsPolicy);
+ mPoliciesSetByAdmins.putAll(policiesSetByAdmins);
mCurrentResolvedPolicy = currentEnforcedPolicy;
}
@@ -63,7 +63,7 @@
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) {
- mAdminsPolicy.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
+ mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
return resolvePolicy();
}
@@ -71,15 +71,19 @@
boolean removePolicy(@NonNull EnforcingAdmin admin) {
Objects.requireNonNull(admin);
- if (mAdminsPolicy.remove(admin) == null) {
+ if (mPoliciesSetByAdmins.remove(admin) == null) {
return false;
}
return resolvePolicy();
}
+ LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
+ return mPoliciesSetByAdmins;
+ }
+
private boolean resolvePolicy() {
- V resolvedPolicy = mPolicyDefinition.resolvePolicy(mAdminsPolicy);
+ V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
mCurrentResolvedPolicy = resolvedPolicy;
@@ -94,14 +98,16 @@
void saveToXml(TypedXmlSerializer serializer) throws IOException {
mPolicyDefinition.saveToXml(serializer);
- mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+ if (mCurrentResolvedPolicy != null) {
+ mPolicyDefinition.savePolicyValueToXml(
+ serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+ }
- for (EnforcingAdmin admin : mAdminsPolicy.keySet()) {
+ for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_POLICY_VALUE, mAdminsPolicy.get(admin));
+ serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin));
serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
admin.saveToXml(serializer);
diff --git a/services/proguard.flags b/services/proguard.flags
index 27fe505..6cdf11c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -88,6 +88,7 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
index 12e7cfc..212ec14 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -18,6 +18,11 @@
package="com.android.frameworks.inputmethodtests">
<uses-sdk android:targetSdkVersion="31" />
+ <queries>
+ <intent>
+ <action android:name="android.view.InputMethod" />
+ </intent>
+ </queries>
<!-- Permissions required for granting and logging -->
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
@@ -29,9 +34,23 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+ <uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
+
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <service android:name="com.android.server.inputmethod.InputMethodBindingControllerTest$EmptyInputMethodService"
+ android:label="Empty IME"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:process=":service"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.view.InputMethod"/>
+ </intent-filter>
+ <meta-data android:name="android.view.im"
+ android:resource="@xml/method"/>
+ </service>
</application>
<instrumentation
diff --git a/services/tests/InputMethodSystemServerTests/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
new file mode 100644
index 0000000..89b06bb
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
new file mode 100644
index 0000000..42d373b
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.inputmethodservice.InputMethodService;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.inputmethod.InputBindResult;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodBindingControllerTest extends InputMethodManagerServiceTestBase {
+
+ private static final String PACKAGE_NAME = "com.android.frameworks.inputmethodtests";
+ private static final String TEST_SERVICE_NAME =
+ "com.android.server.inputmethod.InputMethodBindingControllerTest"
+ + "$EmptyInputMethodService";
+ private static final String TEST_IME_ID = PACKAGE_NAME + "/" + TEST_SERVICE_NAME;
+ private static final long TIMEOUT_IN_SECONDS = 3;
+
+ private InputMethodBindingController mBindingController;
+ private Instrumentation mInstrumentation;
+ private final int mImeConnectionBindFlags =
+ InputMethodBindingController.IME_CONNECTION_BIND_FLAGS
+ & ~Context.BIND_SCHEDULE_LIKE_TOP_APP;
+ private CountDownLatch mCountDownLatch;
+
+ public static class EmptyInputMethodService extends InputMethodService {}
+
+ @Before
+ public void setUp() throws RemoteException {
+ super.setUp();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mCountDownLatch = new CountDownLatch(1);
+ // Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling
+ // from system.
+ mBindingController =
+ new InputMethodBindingController(
+ mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch);
+ }
+
+ @Test
+ public void testBindCurrentMethod_noIme() {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId(null);
+ InputBindResult result = mBindingController.bindCurrentMethod();
+ assertThat(result).isEqualTo(InputBindResult.NO_IME);
+ }
+ }
+
+ @Test
+ public void testBindCurrentMethod_unknownId() {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId("unknown ime id");
+ }
+ assertThrows(IllegalArgumentException.class, () -> {
+ synchronized (ImfLock.class) {
+ mBindingController.bindCurrentMethod();
+ }
+ });
+ }
+
+ @Test
+ public void testBindCurrentMethod_notConnected() {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId(TEST_IME_ID);
+ doReturn(false)
+ .when(mContext)
+ .bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class));
+
+ InputBindResult result = mBindingController.bindCurrentMethod();
+ assertThat(result).isEqualTo(InputBindResult.IME_NOT_CONNECTED);
+ }
+ }
+
+ @Test
+ public void testBindAndUnbindMethod() throws Exception {
+ // Bind with main connection
+ testBindCurrentMethodWithMainConnection();
+
+ // Bind with visible connection
+ testBindCurrentMethodWithVisibleConnection();
+
+ // Unbind both main and visible connections
+ testUnbindCurrentMethod();
+ }
+
+ private void testBindCurrentMethodWithMainConnection() throws Exception {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId(TEST_IME_ID);
+ }
+ InputMethodInfo info = mInputMethodManagerService.mMethodMap.get(TEST_IME_ID);
+ assertThat(info).isNotNull();
+ assertThat(info.getId()).isEqualTo(TEST_IME_ID);
+ assertThat(info.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+
+ // Bind input method with main connection. It is called on another thread because we should
+ // wait for onServiceConnected() to finish.
+ InputBindResult result = callOnMainSync(() -> {
+ synchronized (ImfLock.class) {
+ return mBindingController.bindCurrentMethod();
+ }
+ });
+
+ verify(mContext, times(1))
+ .bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ eq(mImeConnectionBindFlags),
+ any(UserHandle.class));
+ assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING);
+ assertThat(result.id).isEqualTo(info.getId());
+ synchronized (ImfLock.class) {
+ assertThat(mBindingController.hasConnection()).isTrue();
+ assertThat(mBindingController.getCurId()).isEqualTo(info.getId());
+ assertThat(mBindingController.getCurToken()).isNotNull();
+ }
+ // Wait for onServiceConnected()
+ mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+ // Verify onServiceConnected() is called and bound successfully.
+ synchronized (ImfLock.class) {
+ assertThat(mBindingController.getCurMethod()).isNotNull();
+ assertThat(mBindingController.getCurMethodUid()).isNotEqualTo(Process.INVALID_UID);
+ }
+ }
+
+ private void testBindCurrentMethodWithVisibleConnection() {
+ mInstrumentation.runOnMainSync(() -> {
+ synchronized (ImfLock.class) {
+ mBindingController.setCurrentMethodVisible();
+ }
+ });
+ // Bind input method with visible connection
+ verify(mContext, times(1))
+ .bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ eq(InputMethodBindingController.IME_VISIBLE_BIND_FLAGS),
+ any(UserHandle.class));
+ synchronized (ImfLock.class) {
+ assertThat(mBindingController.isVisibleBound()).isTrue();
+ }
+ }
+
+ private void testUnbindCurrentMethod() {
+ mInstrumentation.runOnMainSync(() -> {
+ synchronized (ImfLock.class) {
+ mBindingController.unbindCurrentMethod();
+ }
+ });
+
+ synchronized (ImfLock.class) {
+ // Unbind both main connection and visible connection
+ assertThat(mBindingController.hasConnection()).isFalse();
+ assertThat(mBindingController.isVisibleBound()).isFalse();
+ verify(mContext, times(2)).unbindService(any(ServiceConnection.class));
+ assertThat(mBindingController.getCurToken()).isNull();
+ assertThat(mBindingController.getCurId()).isNull();
+ assertThat(mBindingController.getCurMethod()).isNull();
+ assertThat(mBindingController.getCurMethodUid()).isEqualTo(Process.INVALID_UID);
+ }
+ }
+
+ private static <V> V callOnMainSync(Callable<V> callable) {
+ AtomicReference<V> result = new AtomicReference<>();
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ try {
+ result.set(callable.call());
+ } catch (Exception e) {
+ throw new RuntimeException("Exception was thrown", e);
+ }
+ });
+ return result.get();
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 1f66a11..ee6196d 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -17,7 +17,12 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.Intent
-import android.content.pm.*
+import android.content.pm.ApplicationInfo
+import android.content.pm.ConfigurationInfo
+import android.content.pm.FeatureGroupInfo
+import android.content.pm.FeatureInfo
+import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
@@ -27,7 +32,18 @@
import com.android.internal.R
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.*
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
+import com.android.server.pm.pkg.component.ParsedAttributionImpl
+import com.android.server.pm.pkg.component.ParsedComponentImpl
+import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.server.pm.pkg.component.ParsedPermissionImpl
+import com.android.server.pm.pkg.component.ParsedProcessImpl
+import com.android.server.pm.pkg.component.ParsedProviderImpl
+import com.android.server.pm.pkg.component.ParsedServiceImpl
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import java.security.KeyPairGenerator
@@ -155,7 +171,6 @@
AndroidPackage::getResizeableActivity,
AndroidPackage::getRestrictedAccountType,
AndroidPackage::getRoundIconRes,
- PackageImpl::getSeInfo,
PackageImpl::getSecondaryCpuAbi,
AndroidPackage::getSecondaryNativeLibraryDir,
AndroidPackage::getSharedUserId,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 66e7ec0..c87fd26 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -49,6 +49,7 @@
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.appwidget.AppWidgetManager;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
@@ -538,6 +539,59 @@
}
/**
+ * Verify that we don't let urgent broadcasts starve delivery of non-urgent
+ */
+ @Test
+ public void testUrgentStarvation() {
+ final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+ optInteractive.setInteractive(true);
+
+ mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ // mix of broadcasts, with more than 2 fg/urgent
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+ optInteractive), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+ optInteractive), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+ optInteractive), 0);
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+ queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+ // verify the reset-count-then-resume worked too
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ }
+
+ /**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 5dc1251..be13bad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -48,7 +48,7 @@
StaticMockitoSession mSession;
@Mock
- AppOpsService.Constants mConstants;
+ AppOpsServiceImpl.Constants mConstants;
@Mock
Context mContext;
@@ -57,7 +57,7 @@
Handler mHandler;
@Mock
- AppOpsServiceInterface mLegacyAppOpsService;
+ AppOpsCheckingServiceInterface mLegacyAppOpsService;
AppOpsRestrictions mAppOpsRestrictions;
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index c0688d1..7d4bc6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -22,6 +22,8 @@
import static android.app.AppOpsManager.OP_READ_SMS;
import static android.app.AppOpsManager.OP_WIFI_SCAN;
import static android.app.AppOpsManager.OP_WRITE_SMS;
+import static android.app.AppOpsManager.resolvePackageName;
+import static android.os.Process.INVALID_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,6 +41,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
import android.content.ContentResolver;
@@ -86,13 +89,13 @@
private File mAppOpsFile;
private Handler mHandler;
- private AppOpsService mAppOpsService;
+ private AppOpsServiceImpl mAppOpsService;
private int mMyUid;
private long mTestStartMillis;
private StaticMockitoSession mMockingSession;
private void setupAppOpsService() {
- mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
+ mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext));
mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
// Always approve all permission checks
@@ -161,17 +164,20 @@
@Test
public void testNoteOperationAndGetOpsForPackage() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
// Note an op that's allowed.
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
// Note another op that's not allowed.
- mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
- false);
+ mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -185,18 +191,20 @@
@Test
public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
// This op controls WIFI_SCAN
- mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
- null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
+ assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED);
assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
// Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
- mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
- assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
- null, false).getOpMode()).isEqualTo(MODE_ERRORED);
+ mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null);
+ assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED);
assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -205,11 +213,14 @@
// Tests the dumping and restoring of the in-memory state to/from XML.
@Test
public void testStatePersistence() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
- mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
- false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
mAppOpsService.writeState();
// Create a new app ops service which will initialize its state from XML.
@@ -224,8 +235,10 @@
// Tests that ops are persisted during shutdown.
@Test
public void testShutdown() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
mAppOpsService.shutdown();
// Create a new app ops service which will initialize its state from XML.
@@ -238,8 +251,10 @@
@Test
public void testGetOpsForPackage() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
// Query all ops
List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -267,8 +282,10 @@
@Test
public void testPackageRemoved() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -322,8 +339,10 @@
@Test
public void testUidRemoved() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 98e895a..3efd5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -76,7 +76,7 @@
ActivityManagerInternal mAmi;
@Mock
- AppOpsService.Constants mConstants;
+ AppOpsServiceImpl.Constants mConstants;
AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index e08a715..298dbf4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -93,12 +93,13 @@
}
}
- private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) {
+ private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates,
+ int op1, int op2) {
int numberOfNonDefaultOps = 0;
final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
for(int i = 0; i < uidStates.size(); i++) {
- final AppOpsService.UidState uidState = uidStates.valueAt(i);
+ final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
SparseIntArray opModes = uidState.getNonDefaultUidModes();
if (opModes != null) {
final int uidMode1 = opModes.get(op1, defaultModeOp1);
@@ -112,12 +113,12 @@
continue;
}
for (int j = 0; j < uidState.pkgOps.size(); j++) {
- final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
+ final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j);
if (ops == null) {
continue;
}
- final AppOpsService.Op _op1 = ops.get(op1);
- final AppOpsService.Op _op2 = ops.get(op2);
+ final AppOpsServiceImpl.Op _op1 = ops.get(op1);
+ final AppOpsServiceImpl.Op _op2 = ops.get(op2);
final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
assertEquals(mode1, mode2);
@@ -158,8 +159,8 @@
// Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
- AppOpsService testService = spy(
- new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade
+ AppOpsServiceImpl testService = spy(
+ new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade
assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
mHandler.removeCallbacks(testService.mWriteRunner);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index dd6c733..27d0662 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -139,7 +139,7 @@
.strictness(Strictness.LENIENT)
.mockStatic(SystemProperties::class.java)
.mockStatic(SystemConfig::class.java)
- .mockStatic(SELinuxMMAC::class.java)
+ .mockStatic(SELinuxMMAC::class.java, Mockito.CALLS_REAL_METHODS)
.mockStatic(FallbackCategoryProvider::class.java)
.mockStatic(PackageManagerServiceUtils::class.java)
.mockStatic(Environment::class.java)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
new file mode 100644
index 0000000..ef8a49f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.Sensor;
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class SensorControllerTest {
+
+ private static final int VIRTUAL_DEVICE_ID = 42;
+ private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
+ private static final int SENSOR_HANDLE = 7;
+
+ @Mock
+ private SensorManagerInternal mSensorManagerInternalMock;
+ private SensorController mSensorController;
+ private VirtualSensorEvent mSensorEvent;
+ private VirtualSensorConfig mVirtualSensorConfig;
+ private IBinder mSensorToken;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(SensorManagerInternal.class);
+ LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+ mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+ mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
+ mVirtualSensorConfig =
+ new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+ .build();
+ mSensorToken = new Binder("sensorToken");
+ }
+
+ @Test
+ public void createSensor_invalidHandle_throwsException() {
+ doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
+ anyInt(), anyInt(), anyString(), anyString(), any());
+
+ Throwable thrown = assertThrows(
+ RuntimeException.class,
+ () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+
+ assertThat(thrown.getCause().getMessage())
+ .contains("Received an invalid virtual sensor handle");
+ }
+
+ @Test
+ public void createSensor_success() {
+ doCreateSensorSuccessfully();
+
+ assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+ }
+
+ @Test
+ public void sendSensorEvent_invalidToken_throwsException() {
+ doCreateSensorSuccessfully();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mSensorController.sendSensorEvent(
+ new Binder("invalidSensorToken"), mSensorEvent));
+ }
+
+ @Test
+ public void sendSensorEvent_success() {
+ doCreateSensorSuccessfully();
+
+ mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+ verify(mSensorManagerInternalMock).sendSensorEvent(
+ SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
+ mSensorEvent.getValues());
+ }
+
+ @Test
+ public void unregisterSensor_invalidToken_throwsException() {
+ doCreateSensorSuccessfully();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
+ }
+
+ @Test
+ public void unregisterSensor_success() {
+ doCreateSensorSuccessfully();
+
+ mSensorController.unregisterSensor(mSensorToken);
+ verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+ assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+ }
+
+ private void doCreateSensorSuccessfully() {
+ doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
+ anyInt(), anyInt(), anyString(), anyString(), any());
+ mSensorController.createSensor(mSensorToken, mVirtualSensorConfig);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 0bd6f2c..afaee04 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -51,6 +51,7 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -58,6 +59,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
+import android.hardware.Sensor;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.VirtualKeyEvent;
@@ -88,6 +90,7 @@
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
import org.junit.Before;
import org.junit.Test;
@@ -126,16 +129,19 @@
private static final int VENDOR_ID = 5;
private static final String UNIQUE_ID = "uniqueid";
private static final String PHYS = "phys";
- private static final int DEVICE_ID = 42;
+ private static final int DEVICE_ID = 53;
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
+ private static final int SENSOR_HANDLE = 64;
private static final Binder BINDER = new Binder("binder");
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+ private static final int VIRTUAL_DEVICE_ID = 42;
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
+ private SensorController mSensorController;
private AssociationInfo mAssociationInfo;
private VirtualDeviceManagerService mVdms;
private VirtualDeviceManagerInternal mLocalService;
@@ -150,6 +156,8 @@
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
+ private SensorManagerInternal mSensorManagerInternalMock;
+ @Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@@ -228,6 +236,9 @@
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ LocalServices.removeServiceForTest(SensorManagerInternal.class);
+ LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = UNIQUE_ID;
doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
@@ -252,6 +263,7 @@
mInputController = new InputController(new Object(), mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
+ mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -264,9 +276,9 @@
.setBlockedActivities(getBlockedActivities())
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
- mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
- mActivityListener, mRunningAppsChangedCallback, params);
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+ mInputController, mSensorController, (int associationId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
}
@@ -308,9 +320,9 @@
.addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
- mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
- mActivityListener, mRunningAppsChangedCallback, params);
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+ mInputController, mSensorController, (int associationId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
assertThat(
@@ -576,6 +588,18 @@
}
@Test
+ public void createVirtualSensor_noPermission_failsSecurityException() {
+ doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualSensor(
+ BINDER,
+ new VirtualSensorConfig.Builder(
+ Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+ }
+
+ @Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
@@ -679,6 +703,17 @@
}
@Test
+ public void close_cleanSensorController() {
+ mSensorController.addSensorForTesting(
+ BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME);
+
+ mDeviceImpl.close();
+
+ assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+ verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+ }
+
+ @Test
public void sendKeyEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 036b6df..a226ebc 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -16,9 +16,14 @@
package com.android.server.companion.virtual;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
import static com.google.common.truth.Truth.assertThat;
import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.os.Parcel;
import android.os.UserHandle;
@@ -27,18 +32,25 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class VirtualDeviceParamsTest {
+ private static final String SENSOR_NAME = "VirtualSensorName";
+ private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
@Test
public void parcelable_shouldRecreateSuccessfully() {
VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
- .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
- VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
+ .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .addVirtualSensorConfig(
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setVendor(SENSOR_VENDOR)
+ .build())
.build();
Parcel parcel = Parcel.obtain();
originalParams.writeToParcel(parcel, 0);
@@ -49,7 +61,14 @@
assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
- assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
- .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
+ assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
+
+ List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
+ assertThat(sensorConfigs).hasSize(1);
+ VirtualSensorConfig sensorConfig = sensorConfigs.get(0);
+ assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER);
+ assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME);
+ assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR);
+ assertThat(sensorConfig.getStateChangeCallback()).isNull();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 062bde8..ce35626 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -171,22 +171,6 @@
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
- private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
- new BasicInjector() {
- @Override
- boolean getAllowNonNativeRefreshRateOverride() {
- return true;
- }
- };
-
- private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
- new BasicInjector() {
- @Override
- boolean getAllowNonNativeRefreshRateOverride() {
- return false;
- }
- };
-
@Mock InputManagerInternal mMockInputManagerInternal;
@Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
@Mock IVirtualDisplayCallback.Stub mMockAppToken;
@@ -408,6 +392,75 @@
assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
}
+ @Test
+ public void testCreateVirtualDisplayOwnFocus() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+
+ // This is effectively the DisplayManager service published to ServiceManager.
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+ String uniqueId = "uniqueId --- Own Focus Test";
+ int width = 600;
+ int height = 800;
+ int dpi = 320;
+ int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, width, height, dpi);
+ builder.setFlags(flags);
+ builder.setUniqueId(uniqueId);
+ int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+ /* projection= */ null, PACKAGE_NAME);
+
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+ // flush the handler
+ displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+ DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+ assertNotNull(ddi);
+ assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+ }
+
+ @Test
+ public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+
+ // This is effectively the DisplayManager service published to ServiceManager.
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+ String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
+ int width = 600;
+ int height = 800;
+ int dpi = 320;
+ int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, width, height, dpi);
+ builder.setFlags(flags);
+ builder.setUniqueId(uniqueId);
+ int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+ /* projection= */ null, PACKAGE_NAME);
+
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+ // flush the handler
+ displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+ DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+ assertNotNull(ddi);
+ assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
+ }
+
/**
* Tests that the virtual display is created along-side the default display.
*/
@@ -1044,13 +1097,32 @@
}
/**
- * Tests that the frame rate override is updated accordingly to the
- * allowNonNativeRefreshRateOverride policy.
+ * Tests that the frame rate override is returning the correct value from
+ * DisplayInfo#getRefreshRate
*/
@Test
public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
- testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
- testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid(), 20f)
+ });
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
}
/**
@@ -1078,10 +1150,7 @@
@Test
@DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
- testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ false);
- testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ false);
+ testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
}
/**
@@ -1090,10 +1159,7 @@
@Test
@EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
- testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ true);
- testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ true);
+ testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
}
/**
@@ -1316,10 +1382,9 @@
assertEquals(expectedMode, displayInfo.getMode());
}
- private void testDisplayInfoNonNativeFrameRateOverrideMode(
- DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+ private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
DisplayManagerService displayManager =
- new DisplayManagerService(mContext, injector);
+ new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
registerDefaultDisplays(displayManager);
@@ -1341,40 +1406,12 @@
Display.Mode expectedMode;
if (compatChangeEnabled) {
expectedMode = new Display.Mode(1, 100, 200, 60f);
- } else if (injector.getAllowNonNativeRefreshRateOverride()) {
- expectedMode = new Display.Mode(255, 100, 200, 20f);
} else {
- expectedMode = new Display.Mode(1, 100, 200, 60f);
+ expectedMode = new Display.Mode(255, 100, 200, 20f);
}
assertEquals(expectedMode, displayInfo.getMode());
}
- private void testDisplayInfoNonNativeFrameRateOverride(
- DisplayManagerService.Injector injector) {
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, injector);
- DisplayManagerService.BinderService displayManagerBinderService =
- displayManager.new BinderService();
- registerDefaultDisplays(displayManager);
- displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
- FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
- new float[]{60f});
- int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
- displayDevice);
- DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
- assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
- updateFrameRateOverride(displayManager, displayDevice,
- new DisplayEventReceiver.FrameRateOverride[]{
- new DisplayEventReceiver.FrameRateOverride(
- Process.myUid(), 20f)
- });
- displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
- float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
- assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
- }
-
private int getDisplayIdForDisplayDevice(
DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index c81db92..6258d6d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2014,6 +2014,50 @@
}
@Test
+ public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ createService();
+ startSystem();
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+
+ forceDozing();
+ advanceTime(500);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_DOZING);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+ advanceTime(500);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_DOZING);
+ verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+ }
+
+ @Test
public void testLastSleepTime_notUpdatedWhenDreaming() {
createService();
startSystem();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9090c55..aab70b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -369,7 +369,7 @@
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
token.finishSync(t, false /* cancel */);
transit.onTransactionReady(transit.getSyncId(), t);
- dc.mTransitionController.finishTransition(transit);
+ dc.mTransitionController.finishTransition(transit.getToken());
assertFalse(wallpaperWindow.isVisible());
assertFalse(token.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4429aef..871030f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -16,12 +16,16 @@
package com.android.server.wm;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -81,6 +85,9 @@
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import org.junit.Rule;
import org.junit.Test;
@@ -99,6 +106,11 @@
@Rule
public ExpectedException mExpectedException = ExpectedException.none();
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ADD_TRUSTED_DISPLAY);
+
@Test
public void testAddWindowToken() {
IBinder token = mock(IBinder.class);
@@ -396,9 +408,15 @@
@Test
public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
// Create one extra display
- final VirtualDisplay virtualDisplay = createVirtualDisplay();
+ final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+ final VirtualDisplay virtualDisplayOwnTouchMode =
+ createVirtualDisplay(/* ownFocus= */ true);
final int numberOfDisplays = mWm.mRoot.mChildren.size();
- assertThat(numberOfDisplays).isAtLeast(2);
+ assertThat(numberOfDisplays).isAtLeast(3);
+ final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
+ .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+ .count();
+ assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
// Enable global touch mode (config_perDisplayFocusEnabled set to false)
Resources mockResources = mock(Resources.class);
@@ -417,15 +435,15 @@
mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
- verify(mWm.mInputManager, times(numberOfDisplays)).setInTouchMode(
+ verify(mWm.mInputManager, times(numberOfGlobalTouchModeDisplays)).setInTouchMode(
eq(!currentTouchMode), eq(callingPid), eq(callingUid),
/* hasPermission= */ eq(true), /* displayId= */ anyInt());
}
@Test
- public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
+ public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
// Create one extra display
- final VirtualDisplay virtualDisplay = createVirtualDisplay();
+ final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
final int numberOfDisplays = mWm.mRoot.mChildren.size();
assertThat(numberOfDisplays).isAtLeast(2);
@@ -452,14 +470,47 @@
virtualDisplay.getDisplay().getDisplayId());
}
- private VirtualDisplay createVirtualDisplay() {
+ @Test
+ public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
+ // Create one extra display
+ final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
+ final int numberOfDisplays = mWm.mRoot.mChildren.size();
+ assertThat(numberOfDisplays).isAtLeast(2);
+
+ // Enable global touch mode (config_perDisplayFocusEnabled set to false)
+ Resources mockResources = mock(Resources.class);
+ spyOn(mContext);
+ when(mContext.getResources()).thenReturn(mockResources);
+ doReturn(false).when(mockResources).getBoolean(
+ com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+ // Get current touch mode state and setup WMS to run setInTouchMode
+ boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+ when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+ android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+ mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+
+ // Ensure that new display touch mode state has changed.
+ verify(mWm.mInputManager).setInTouchMode(
+ !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
+ virtualDisplay.getDisplay().getDisplayId());
+ }
+
+ private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
// Create virtual display
Point surfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC;
+ if (ownFocus) {
+ flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED;
+ }
VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
- surfaceSize.x, surfaceSize.y,
- DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_PUBLIC);
+ surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags);
final int displayId = virtualDisplay.getDisplay().getDisplayId();
mWm.mRoot.onDisplayAdded(displayId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 1348770..5a261bc65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1746,7 +1746,7 @@
}
void startTransition() {
- mOrganizer.startTransition(mLastTransit, null);
+ mOrganizer.startTransition(mLastTransit.getToken(), null);
}
void onTransactionReady(SurfaceControl.Transaction t) {
@@ -1759,7 +1759,7 @@
}
public void finish() {
- mController.finishTransition(mLastTransit);
+ mController.finishTransition(mLastTransit.getToken());
}
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ed96a9b..22cd31a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1996,6 +1996,15 @@
"nr_advanced_threshold_bandwidth_khz_int";
/**
+ * Indicating whether to include LTE cell bandwidths when determining whether the aggregated
+ * cell bandwidth meets the required threshold for NR advanced.
+ *
+ * @see TelephonyDisplayInfo#OVERRIDE_NETWORK_TYPE_NR_ADVANCED
+ */
+ public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL =
+ "include_lte_for_nr_advanced_threshold_bandwidth_bool";
+
+ /**
* Boolean indicating if operator name should be shown in the status bar
* @hide
*/
@@ -9577,6 +9586,7 @@
sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
+ sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 0c14dba..a1257e3 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,8 @@
int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+ int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+ int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -620,4 +622,5 @@
int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107;
int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108;
int RIL_UNSOL_NOTIFY_ANBR = 1109;
+ int RIL_UNSOL_ON_NETWORK_INITIATED_LOCATION_RESULT = 1110;
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 8a1e1fa..3f6a75d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -172,4 +172,17 @@
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
}
+
+ open fun cujCompleted() {
+ entireScreenCovered()
+ navBarLayerIsVisibleAtStartAndEnd()
+ navBarWindowIsAlwaysVisible()
+ taskBarLayerIsVisibleAtStartAndEnd()
+ taskBarWindowIsAlwaysVisible()
+ statusBarLayerIsVisibleAtStartAndEnd()
+ statusBarLayerPositionAtStartAndEnd()
+ statusBarWindowIsAlwaysVisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
new file mode 100644
index 0000000..945de33
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "ironwood-postsubmit": [
+ {
+ "name": "FlickerTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.IwTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index b9c875a..ef42766 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -101,6 +102,16 @@
testSpec.assertWm { this.isAppWindowOnTop(testApp) }
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ navBarLayerPositionAtStartAndEnd()
+ imeLayerBecomesInvisible()
+ imeAppLayerIsAlwaysVisible()
+ imeAppWindowIsAlwaysVisible()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 1dc3ca5..c92fce3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.ime
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -100,6 +101,17 @@
testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ navBarLayerPositionAtStartAndEnd()
+ imeLayerBecomesInvisible()
+ imeAppWindowBecomesInvisible()
+ imeWindowBecomesInvisible()
+ imeLayerBecomesInvisible()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index a6bd791..7d7953b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -79,6 +80,14 @@
super.visibleLayersShownMoreThanOneConsecutiveEntry()
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ imeLayerBecomesInvisible()
+ imeWindowBecomesInvisible()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index b43efea..9919d87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.ime
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -50,6 +51,15 @@
}
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ imeWindowBecomesVisible()
+ appWindowAlwaysVisibleOnTop()
+ layerAlwaysVisible()
+ }
+
@Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
@Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1973ec0..ad14d0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -125,6 +126,14 @@
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ focusChanges()
+ rotationLayerAppearsAndVanishes()
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 4faeb24..8e3fd40 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -73,4 +73,10 @@
}
}
}
+
+ override fun cujCompleted() {
+ super.cujCompleted()
+ appLayerRotates_StartingPos()
+ appLayerRotates_EndingPos()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a08db29..d0d4122 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.WindowManager
@@ -204,6 +205,31 @@
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ if (!testSpec.isTablet) {
+ // not yet tablet compatible
+ appLayerRotates()
+ appLayerAlwaysVisible()
+ }
+
+ appWindowFullScreen()
+ appWindowSeamlessRotation()
+ focusDoesNotChange()
+ statusBarLayerIsAlwaysInvisible()
+ statusBarWindowIsAlwaysInvisible()
+ appLayerRotates_StartingPos()
+ appLayerRotates_EndingPos()
+ entireScreenCovered()
+ navBarLayerIsVisibleAtStartAndEnd()
+ navBarWindowIsAlwaysVisible()
+ taskBarLayerIsVisibleAtStartAndEnd()
+ taskBarWindowIsAlwaysVisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
private val FlickerTestParameter.starveUiThread
get() =
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c4c002d..d60869a 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1076,7 +1076,7 @@
/** Adds a feature to the feature group. */
void AddFeature(const std::string& name, bool required = true, int32_t version = -1) {
- features_.insert(std::make_pair(name, Feature{ required, version }));
+ features_.insert_or_assign(name, Feature{required, version});
if (required) {
if (name == "android.hardware.camera.autofocus" ||
name == "android.hardware.camera.flash") {
@@ -1348,6 +1348,11 @@
std::string impliedReason;
void Extract(xml::Element* element) override {
+ const auto parent_stack = extractor()->parent_stack();
+ if (!extractor()->options_.only_permissions &&
+ (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+ return;
+ }
name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
std::string feature =
GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
@@ -1472,6 +1477,11 @@
const int32_t* maxSdkVersion = nullptr;
void Extract(xml::Element* element) override {
+ const auto parent_stack = extractor()->parent_stack();
+ if (!extractor()->options_.only_permissions &&
+ (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+ return;
+ }
name = GetAttributeString(FindAttribute(element, NAME_ATTR));
maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR));