Merge "Prevent system restart while updating package during boot" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 29349cb..9a417e9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,11 +19,14 @@
// Add java_aconfig_libraries to here to add them to the core framework
srcs: [
":android.os.flags-aconfig-java{.generated_srcjars}",
+ ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
+ ":telecom_flags_core_java_lib{.generated_srcjars}",
":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
],
// Add aconfig-annotations-lib as a dependency for the optimization
libs: ["aconfig-annotations-lib"],
@@ -44,6 +47,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Telecom
+java_aconfig_library {
+ name: "telecom_flags_core_java_lib",
+ aconfig_declarations: "telecom_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Window
aconfig_declarations {
name: "com.android.window.flags.window-aconfig",
@@ -70,6 +80,11 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "aconfig_text_flags_c_lib",
+ aconfig_declarations: "com.android.text.flags-aconfig",
+}
+
// Security
aconfig_declarations {
name: "android.security.flags-aconfig",
@@ -116,3 +131,29 @@
package: "android.companion.virtual.flags",
srcs: ["core/java/android/companion/virtual/*.aconfig"],
}
+
+// InputMethod
+aconfig_declarations {
+ name: "android.view.inputmethod.flags-aconfig",
+ package: "android.view.inputmethod",
+ srcs: ["core/java/android/view/inputmethod/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.view.inputmethod.flags-aconfig-java",
+ aconfig_declarations: "android.view.inputmethod.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Vibrator
+aconfig_declarations {
+ name: "android.os.vibrator.flags-aconfig",
+ package: "android.os.vibrator",
+ srcs: ["core/java/android/os/vibrator/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.os.vibrator.flags-aconfig-java",
+ aconfig_declarations: "android.os.vibrator.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 17076bc..66c1efc 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -419,6 +419,14 @@
*/
public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327;
+ /**
+ * Granted by {@link com.android.server.pm.PackageArchiverService} to the installer responsible
+ * for unarchiving an app.
+ *
+ * @hide
+ */
+ public static final int REASON_PACKAGE_UNARCHIVE = 328;
+
/** @hide The app requests out-out. */
public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -502,6 +510,7 @@
REASON_ACTIVE_DEVICE_ADMIN,
REASON_MEDIA_NOTIFICATION_TRANSFER,
REASON_PACKAGE_INSTALLER,
+ REASON_PACKAGE_UNARCHIVE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReasonCode {}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 1be07fd..b8596d5 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -538,7 +538,7 @@
/**
* Package names the system has white-listed to opt out of power save restrictions,
- * except for device idle mode.
+ * except for device idle modes (light and full doze).
*/
private final ArrayMap<String, Integer> mPowerSaveWhitelistAppsExceptIdle = new ArrayMap<>();
diff --git a/core/api/current.txt b/core/api/current.txt
index 4d93e15..df41b1f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -42273,7 +42273,7 @@
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000
field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
- field public static final int PROPERTY_IS_TRANSACTIONAL = 32768; // 0x8000
+ field @FlaggedApi(Flags.FLAG_VOIP_APP_ACTIONS_SUPPORT) public static final int PROPERTY_IS_TRANSACTIONAL = 32768; // 0x8000
field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800
field public static final int PROPERTY_RTT = 1024; // 0x400
field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index adf1da9..0c61981 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3213,6 +3213,7 @@
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
+ method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
}
@@ -3546,6 +3547,7 @@
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
+ field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -3795,6 +3797,13 @@
public class PackageArchiver {
method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ field public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+ field public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+ }
+
+ public class PackageInfo implements android.os.Parcelable {
+ field public boolean isArchived;
}
public class PackageInstaller {
@@ -4016,6 +4025,7 @@
field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
+ field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -7281,8 +7291,11 @@
package android.media.audiopolicy {
- public class AudioMix {
+ public class AudioMix implements android.os.Parcelable {
+ method public int describeContents();
method public int getMixState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioMix> CREATOR;
field public static final int MIX_STATE_DISABLED = -1; // 0xffffffff
field public static final int MIX_STATE_IDLE = 0; // 0x0
field public static final int MIX_STATE_MIXING = 1; // 0x1
@@ -7291,15 +7304,18 @@
}
public static class AudioMix.Builder {
- ctor public AudioMix.Builder(android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
+ ctor public AudioMix.Builder(@NonNull android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
method public android.media.audiopolicy.AudioMix build() throws java.lang.IllegalArgumentException;
method public android.media.audiopolicy.AudioMix.Builder setDevice(@NonNull android.media.AudioDeviceInfo) throws java.lang.IllegalArgumentException;
- method public android.media.audiopolicy.AudioMix.Builder setFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
+ method public android.media.audiopolicy.AudioMix.Builder setFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method public android.media.audiopolicy.AudioMix.Builder setRouteFlags(int) throws java.lang.IllegalArgumentException;
}
- public class AudioMixingRule {
+ public class AudioMixingRule implements android.os.Parcelable {
+ method public int describeContents();
method public int getTargetMixRole();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioMixingRule> CREATOR;
field public static final int MIX_ROLE_INJECTOR = 1; // 0x1
field public static final int MIX_ROLE_PLAYERS = 0; // 0x0
field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2
@@ -7336,6 +7352,7 @@
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public String toLogFriendlyString();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 56fef1a..dab4110 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1486,13 +1486,13 @@
AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
- * Allows the assistant app to get the negative trigger data from the PCC sandbox to improve the
+ * Allows the assistant app to get the training data from the PCC sandbox to improve the
* hotword training model.
*
* @hide
*/
- public static final int OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO =
- AppProtoEnums.APP_OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO;
+ public static final int OP_RECEIVE_SANDBOX_TRAINING_DATA =
+ AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRAINING_DATA;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1640,7 +1640,7 @@
OPSTR_CAMERA_SANDBOXED,
OPSTR_RECORD_AUDIO_SANDBOXED,
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO
+ OPSTR_RECEIVE_SANDBOX_TRAINING_DATA
})
public @interface AppOpString {}
@@ -2261,13 +2261,13 @@
"android:receive_sandbox_trigger_audio";
/**
- * Allows the assistant app to get the negative trigger data from the PCC sandbox to improve
+ * Allows the assistant app to get the training data from the PCC sandbox to improve
* the hotword training model.
*
* @hide
*/
- public static final String OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO =
- "android:receive_sandbox_negative_data_audio";
+ public static final String OPSTR_RECEIVE_SANDBOX_TRAINING_DATA =
+ "android:receive_sandbox_training_data";
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2811,9 +2811,9 @@
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
"RECEIVE_SANDBOX_TRIGGER_AUDIO")
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
- new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO,
- OPSTR_RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO,
- "RECEIVE_SANDBOX_NEGATIVE_DATA_AUDIO").build()
+ new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_TRAINING_DATA,
+ OPSTR_RECEIVE_SANDBOX_TRAINING_DATA,
+ "RECEIVE_SANDBOX_TRAINING_DATA").build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index d66fca8..ed0f872 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2495,7 +2495,8 @@
+ ", hints=" + Arrays.toString(node.getAutofillHints())
+ ", value=" + node.getAutofillValue()
+ ", sanitized=" + node.isSanitized()
- + ", important=" + node.getImportantForAutofill());
+ + ", important=" + node.getImportantForAutofill()
+ + ", visibility=" + node.getVisibility());
}
final int NCHILDREN = node.getChildCount();
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 4801d15..be699f4 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -70,6 +70,12 @@
void close();
/**
+ * Specifies a policy for this virtual device.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void setDevicePolicy(int policyType, int devicePolicy);
+
+ /**
* Notifies that an audio session being started.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d13bfd4..2e5c0f7 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -238,6 +238,15 @@
}
}
+ void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ try {
+ mVirtualDevice.setDevicePolicy(policyType, devicePolicy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index af41370..923e689 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -19,6 +19,7 @@
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -59,6 +60,8 @@
import android.util.Log;
import android.view.Surface;
+import com.android.internal.util.AnnotationValidations;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -513,6 +516,28 @@
}
/**
+ * Specifies a policy for this virtual device.
+ *
+ * <p>Policies define the system behavior that may be specific for this virtual device. The
+ * given policy must be able to be changed dynamically during the lifetime of the device.
+ *
+ * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+ * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+ *
+ * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+ */
+ @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ AnnotationValidations.validate(
+ VirtualDeviceParams.DynamicPolicyType.class, null, policyType);
+ AnnotationValidations.validate(
+ VirtualDeviceParams.DevicePolicy.class, null, devicePolicy);
+ mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
+ }
+
+ /**
* Creates a virtual dpad.
*
* @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index b6d8375..037e814a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -150,6 +150,17 @@
public @interface PolicyType {}
/**
+ * Policy types that can be dynamically changed during the virtual device's lifetime.
+ *
+ * @see VirtualDeviceManager.VirtualDevice#setDevicePolicy
+ * @hide
+ */
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface DynamicPolicyType {}
+
+ /**
* Tells the sensor framework how to handle sensor requests from contexts associated with this
* virtual device, namely the sensors returned by
* {@link android.hardware.SensorManager#getSensorList}:
@@ -375,6 +386,14 @@
}
/**
+ * Returns all device policies.
+ * @hide
+ */
+ public @NonNull SparseIntArray getDevicePolicies() {
+ return mDevicePolicies;
+ }
+
+ /**
* Returns the configurations for all sensors that should be created for this device.
*
* @see Builder#addVirtualSensorConfig
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 39b99c6..057b856 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -6,3 +6,10 @@
description: "More logs to test flags with"
bug: "291725823"
}
+
+flag {
+ name: "dynamic_policy"
+ namespace: "virtual_devices"
+ description: "Enable dynamic policy API"
+ bug: "298401780"
+}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 8f35ca2..15678a7 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -226,6 +226,11 @@
}
/** @hide */
+ public AttributionSource withDefaultToken() {
+ return withToken(sDefaultToken);
+ }
+
+ /** @hide */
public AttributionSource withPid(int pid) {
return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
@@ -552,16 +557,28 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AttributionSource that = (AttributionSource) o;
- return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+ return equalsExceptToken(that) && Objects.equals(
+ mAttributionSourceState.token, that.mAttributionSourceState.token);
+ }
+
+ /**
+ * We store trusted attribution sources without their token (the token is the key to the map)
+ * to avoid having a strong reference to the token. This means, when checking the equality of a
+ * supplied AttributionSource in PermissionManagerService.isTrustedAttributionSource, we want to
+ * compare everything except the token.
+ *
+ * @hide
+ */
+ public boolean equalsExceptToken(@Nullable AttributionSource o) {
+ if (o == null) return false;
+ return mAttributionSourceState.uid == o.mAttributionSourceState.uid
&& Objects.equals(mAttributionSourceState.packageName,
- that.mAttributionSourceState.packageName)
+ o.mAttributionSourceState.packageName)
&& Objects.equals(mAttributionSourceState.attributionTag,
- that.mAttributionSourceState.attributionTag)
- && Objects.equals(mAttributionSourceState.token,
- that.mAttributionSourceState.token)
+ o.mAttributionSourceState.attributionTag)
&& Arrays.equals(mAttributionSourceState.renouncedPermissions,
- that.mAttributionSourceState.renouncedPermissions)
- && Objects.equals(getNext(), that.getNext());
+ o.mAttributionSourceState.renouncedPermissions)
+ && Objects.equals(getNext(), o.getNext());
}
@Override
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 31f6418..fe7d1e6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5270,6 +5270,16 @@
public static final String ACTION_SHOW_FOREGROUND_SERVICE_MANAGER =
"android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER";
+ /**
+ * Broadcast Action: Sent to the responsible installer of an archived package when unarchival
+ * is requested.
+ *
+ * @see android.content.pm.PackageArchiver
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl
index fc471c4..dc6491d 100644
--- a/core/java/android/content/pm/IPackageArchiverService.aidl
+++ b/core/java/android/content/pm/IPackageArchiverService.aidl
@@ -23,4 +23,7 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+ void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java
index d739d50..b065231 100644
--- a/core/java/android/content/pm/PackageArchiver.java
+++ b/core/java/android/content/pm/PackageArchiver.java
@@ -42,6 +42,26 @@
@SystemApi
public class PackageArchiver {
+ /**
+ * Extra field for the package name of a package that is requested to be unarchived. Sent as
+ * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
+ "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+
+ /**
+ * If true, the requestor of the unarchival has specified that the app should be unarchived
+ * for {@link android.os.UserHandle#ALL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_UNARCHIVE_ALL_USERS =
+ "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+
private final Context mContext;
private final IPackageArchiverService mService;
@@ -58,7 +78,7 @@
*
* @param statusReceiver Callback used to notify when the operation is completed.
* @throws NameNotFoundException If {@code packageName} isn't found or not available to the
- * caller.
+ * caller or isn't archived.
* @hide
*/
@RequiresPermission(anyOf = {
@@ -76,4 +96,34 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Requests to unarchive a currently archived package.
+ *
+ * <p> Sends a request to unarchive an app to the responsible installer. The installer is
+ * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or
+ * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null.
+ *
+ * <p> The installation will happen asynchronously and can be observed through
+ * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
+ *
+ * @throws NameNotFoundException If {@code packageName} isn't found or not visible to the
+ * caller or if the package has no installer on the device
+ * anymore to unarchive it.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES})
+ @SystemApi
+ public void requestUnarchive(@NonNull String packageName)
+ throws NameNotFoundException {
+ try {
+ mService.requestUnarchive(packageName, mContext.getPackageName(), mContext.getUser());
+ } catch (ParcelableException e) {
+ e.maybeRethrow(NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 63c11b7..cdb8b46 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -488,6 +489,17 @@
*/
public boolean isActiveApex;
+ /**
+ * Whether the package is currently in an archived state.
+ *
+ * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored
+ * on the device, but do keep the data directory.
+ * @hide
+ */
+ // TODO(b/278553670) Unhide and update @links before launch.
+ @SystemApi
+ public boolean isArchived;
+
public PackageInfo() {
}
@@ -575,6 +587,7 @@
}
dest.writeBoolean(isApex);
dest.writeBoolean(isActiveApex);
+ dest.writeBoolean(isArchived);
dest.restoreAllowSquashing(prevAllowSquashing);
}
@@ -640,5 +653,6 @@
}
isApex = source.readBoolean();
isActiveApex = source.readBoolean();
+ isArchived = source.readBoolean();
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c384389..9a53a2a6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -787,6 +787,7 @@
MATCH_DEBUG_TRIAGED_MISSING,
MATCH_INSTANT,
MATCH_APEX,
+ MATCH_ARCHIVED_PACKAGES,
GET_DISABLED_COMPONENTS,
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
@@ -811,6 +812,7 @@
GET_UNINSTALLED_PACKAGES,
MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
MATCH_APEX,
+ MATCH_ARCHIVED_PACKAGES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoFlagsBits {}
@@ -1235,6 +1237,21 @@
public static final long GET_ATTRIBUTIONS_LONG = 0x80000000L;
/**
+ * Flag parameter to also retrieve some information about archived packages.
+ * Packages can be archived through {@link PackageArchiver} and do not have any APKs stored on
+ * the device, but do keep the data directory.
+ * <p> Note: Archived apps are a subset of apps returned by {@link #MATCH_UNINSTALLED_PACKAGES}.
+ * <p> Note: this flag may cause less information about currently installed
+ * applications to be returned.
+ * <p> Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES
+ * permission to see uninstalled packages.
+ * @hide
+ */
+ // TODO(b/278553670) Unhide and update @links before launch.
+ @SystemApi
+ public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
+
+ /**
* @hide
*/
public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L;
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index 7a153ef..c5f5614 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -116,8 +116,9 @@
* Builder for DeviceBatteryConsumer.
*/
public static final class Builder extends BaseBuilder<AggregateBatteryConsumer.Builder> {
- public Builder(BatteryConsumer.BatteryConsumerData data, int scope) {
- super(data, CONSUMER_TYPE_AGGREGATE);
+ public Builder(BatteryConsumer.BatteryConsumerData data, int scope,
+ double minConsumedPowerThreshold) {
+ super(data, CONSUMER_TYPE_AGGREGATE, minConsumedPowerThreshold);
data.putInt(COLUMN_INDEX_SCOPE, scope);
}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 0ba8d51..ca84b35 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -795,11 +795,12 @@
protected final BatteryConsumer.BatteryConsumerData mData;
protected final PowerComponents.Builder mPowerComponentsBuilder;
- public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType) {
+ public BaseBuilder(BatteryConsumer.BatteryConsumerData data, int consumerType,
+ double minConsumedPowerThreshold) {
mData = data;
data.putLong(COLUMN_INDEX_BATTERY_CONSUMER_TYPE, consumerType);
- mPowerComponentsBuilder = new PowerComponents.Builder(data);
+ mPowerComponentsBuilder = new PowerComponents.Builder(data, minConsumedPowerThreshold);
}
@Nullable
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 7586bf7..a5f8844 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -707,7 +707,7 @@
XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
builder = new Builder(customComponentNames.toArray(new String[0]), true,
- includesProcStateData);
+ includesProcStateData, 0);
builder.setStatsStartTimestamp(
parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
@@ -782,6 +782,7 @@
private final String[] mCustomPowerComponentNames;
private final boolean mIncludePowerModels;
private final boolean mIncludesProcessStateData;
+ private final double mMinConsumedPowerThreshold;
private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
private long mStatsStartTimestampMs;
private long mStatsEndTimestampMs;
@@ -802,11 +803,11 @@
private BatteryStatsHistory mBatteryStatsHistory;
public Builder(@NonNull String[] customPowerComponentNames) {
- this(customPowerComponentNames, false, false);
+ this(customPowerComponentNames, false, false, 0);
}
public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
- boolean includeProcessStateData) {
+ boolean includeProcessStateData, double minConsumedPowerThreshold) {
mBatteryConsumersCursorWindow =
new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
mBatteryConsumerDataLayout =
@@ -817,12 +818,14 @@
mCustomPowerComponentNames = customPowerComponentNames;
mIncludePowerModels = includePowerModels;
mIncludesProcessStateData = includeProcessStateData;
+ mMinConsumedPowerThreshold = minConsumedPowerThreshold;
for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
mAggregateBatteryConsumersBuilders[scope] =
- new AggregateBatteryConsumer.Builder(data, scope);
+ new AggregateBatteryConsumer.Builder(
+ data, scope, mMinConsumedPowerThreshold);
}
}
@@ -961,7 +964,8 @@
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
- builder = new UidBatteryConsumer.Builder(data, batteryStatsUid);
+ builder = new UidBatteryConsumer.Builder(data, batteryStatsUid,
+ mMinConsumedPowerThreshold);
mUidBatteryConsumerBuilders.put(uid, builder);
}
return builder;
@@ -979,7 +983,7 @@
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
- builder = new UidBatteryConsumer.Builder(data, uid);
+ builder = new UidBatteryConsumer.Builder(data, uid, mMinConsumedPowerThreshold);
mUidBatteryConsumerBuilders.put(uid, builder);
}
return builder;
@@ -996,7 +1000,7 @@
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
- builder = new UserBatteryConsumer.Builder(data, userId);
+ builder = new UserBatteryConsumer.Builder(data, userId, mMinConsumedPowerThreshold);
mUserBatteryConsumerBuilders.put(userId, builder);
}
return builder;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index b3f4d98..49d7e8b 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -80,6 +80,7 @@
private final long mMaxStatsAgeMs;
private final long mFromTimestamp;
private final long mToTimestamp;
+ private final double mMinConsumedPowerThreshold;
private final @BatteryConsumer.PowerComponent int[] mPowerComponents;
private BatteryUsageStatsQuery(@NonNull Builder builder) {
@@ -87,6 +88,7 @@
mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
: new int[]{UserHandle.USER_ALL};
mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
+ mMinConsumedPowerThreshold = builder.mMinConsumedPowerThreshold;
mFromTimestamp = builder.mFromTimestamp;
mToTimestamp = builder.mToTimestamp;
mPowerComponents = builder.mPowerComponents;
@@ -137,6 +139,14 @@
}
/**
+ * Returns the minimal power component consumed power threshold. The small power consuming
+ * components will be reported as zero.
+ */
+ public double getMinConsumedPowerThreshold() {
+ return mMinConsumedPowerThreshold;
+ }
+
+ /**
* Returns the exclusive lower bound of the stored snapshot timestamps that should be included
* in the aggregation. Ignored if {@link #getToTimestamp()} is zero.
*/
@@ -158,6 +168,7 @@
mUserIds = new int[in.readInt()];
in.readIntArray(mUserIds);
mMaxStatsAgeMs = in.readLong();
+ mMinConsumedPowerThreshold = in.readDouble();
mFromTimestamp = in.readLong();
mToTimestamp = in.readLong();
mPowerComponents = in.createIntArray();
@@ -169,6 +180,7 @@
dest.writeInt(mUserIds.length);
dest.writeIntArray(mUserIds);
dest.writeLong(mMaxStatsAgeMs);
+ dest.writeDouble(mMinConsumedPowerThreshold);
dest.writeLong(mFromTimestamp);
dest.writeLong(mToTimestamp);
dest.writeIntArray(mPowerComponents);
@@ -202,6 +214,7 @@
private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
private long mFromTimestamp;
private long mToTimestamp;
+ private double mMinConsumedPowerThreshold = 0;
private @BatteryConsumer.PowerComponent int[] mPowerComponents;
/**
@@ -301,5 +314,14 @@
mMaxStatsAgeMs = maxStatsAgeMs;
return this;
}
+
+ /**
+ * Set the minimal power component consumed power threshold. The small power consuming
+ * components will be reported as zero.
+ */
+ public Builder setMinConsumedPowerThreshold(double minConsumedPowerThreshold) {
+ mMinConsumedPowerThreshold = minConsumedPowerThreshold;
+ return this;
+ }
}
}
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 6275352..f30dd20 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -36,4 +36,10 @@
void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration,
in VibrationAttributes attributes, String reason, IBinder token);
void cancelVibrate(int usageFilter, IBinder token);
+
+ // Async oneway APIs.
+ // There is no order guarantee with respect to the two-way APIs above like
+ // vibrate/isVibrating/cancel.
+ oneway void performHapticFeedback(int uid, int displayId, String opPkg, int constant,
+ boolean always, String reason, IBinder token);
}
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 5dffa0a..9e5f539 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -461,9 +461,11 @@
private static final byte POWER_MODEL_UNINITIALIZED = -1;
private final BatteryConsumer.BatteryConsumerData mData;
+ private final double mMinConsumedPowerThreshold;
- Builder(BatteryConsumer.BatteryConsumerData data) {
+ Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
mData = data;
+ mMinConsumedPowerThreshold = minConsumedPowerThreshold;
for (BatteryConsumer.Key[] keys : mData.layout.keys) {
for (BatteryConsumer.Key key : keys) {
if (key.mPowerModelColumnIndex != -1) {
@@ -476,6 +478,9 @@
@NonNull
public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
int powerModel) {
+ if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
+ componentPower = 0;
+ }
mData.putDouble(key.mPowerColumnIndex, componentPower);
if (key.mPowerModelColumnIndex != -1) {
mData.putInt(key.mPowerModelColumnIndex, powerModel);
@@ -491,6 +496,9 @@
*/
@NonNull
public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
+ if (Math.abs(componentPower) < mMinConsumedPowerThreshold) {
+ componentPower = 0;
+ }
final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
if (index < 0 || index >= mData.layout.customPowerComponentCount) {
throw new IllegalArgumentException(
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 1cd0f3b..04c257b 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -206,6 +206,15 @@
}
@Override
+ public void performHapticFeedback(int constant, boolean always, String reason) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager.");
+ return;
+ }
+ mVibratorManager.performHapticFeedback(constant, always, reason);
+ }
+
+ @Override
public void cancel() {
if (mVibratorManager == null) {
Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index 284b246..ee90834 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -145,6 +145,21 @@
}
@Override
+ public void performHapticFeedback(int constant, boolean always, String reason) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.performHapticFeedback(
+ Process.myUid(), mContext.getAssociatedDisplayId(), mPackageName, constant,
+ always, reason, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to perform haptic feedback.", e);
+ }
+ }
+
+ @Override
public void cancel() {
cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL);
}
@@ -228,6 +243,11 @@
}
@Override
+ public void performHapticFeedback(int effectId, boolean always, String reason) {
+ SystemVibratorManager.this.performHapticFeedback(effectId, always, reason);
+ }
+
+ @Override
public void cancel() {
SystemVibratorManager.this.cancel();
}
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 103452d..03a1b6f 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -207,17 +207,18 @@
private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED;
private boolean mExcludeFromBatteryUsageStats;
- public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid) {
- this(data, batteryStatsUid, batteryStatsUid.getUid());
+ public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid,
+ double minConsumedPowerThreshold) {
+ this(data, batteryStatsUid, batteryStatsUid.getUid(), minConsumedPowerThreshold);
}
- public Builder(BatteryConsumerData data, int uid) {
- this(data, null, uid);
+ public Builder(BatteryConsumerData data, int uid, double minConsumedPowerThreshold) {
+ this(data, null, uid, minConsumedPowerThreshold);
}
private Builder(BatteryConsumerData data, @Nullable BatteryStats.Uid batteryStatsUid,
- int uid) {
- super(data, CONSUMER_TYPE_UID);
+ int uid, double minConsumedPowerThreshold) {
+ super(data, CONSUMER_TYPE_UID, minConsumedPowerThreshold);
mBatteryStatsUid = batteryStatsUid;
mUid = uid;
mIsVirtualUid = mUid == Process.SDK_SANDBOX_VIRTUAL_UID;
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
index 6b4a5cf..a2ff078 100644
--- a/core/java/android/os/UserBatteryConsumer.java
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -107,8 +107,8 @@
public static final class Builder extends BaseBuilder<Builder> {
private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
- Builder(BatteryConsumerData data, int userId) {
- super(data, CONSUMER_TYPE_USER);
+ Builder(BatteryConsumerData data, int userId, double minConsumedPowerThreshold) {
+ super(data, CONSUMER_TYPE_USER, minConsumedPowerThreshold);
data.putLong(COLUMN_INDEX_USER_ID, userId);
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index aafa501..99c9925 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -510,6 +510,28 @@
String reason, @NonNull VibrationAttributes attributes);
/**
+ * Performs a haptic feedback.
+ *
+ * <p>A haptic feedback is a short vibration feedback. The type of feedback is identified via
+ * the {@code constant}, which should be one of the effect constants provided in
+ * {@link HapticFeedbackConstants}. The haptic feedback provided for a given effect ID is
+ * consistent across all usages on the same device.
+ *
+ * @param constant the ID for the haptic feedback. This should be one of the constants defined
+ * in {@link HapticFeedbackConstants}.
+ * @param always {@code true} if the haptic feedback should be played regardless of the user
+ * vibration intensity settings applicable to the corresponding vibration.
+ * {@code false} if the vibration for the haptic feedback should respect the applicable
+ * vibration intensity settings.
+ * @param reason the reason for this haptic feedback.
+ *
+ * @hide
+ */
+ public void performHapticFeedback(int constant, boolean always, String reason) {
+ Log.w(TAG, "performHapticFeedback is not supported");
+ }
+
+ /**
* Query whether the vibrator natively supports the given effects.
*
* <p>If an effect is not supported, the system may still automatically fall back to playing
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index f506ef8..e0b6a9f 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -35,7 +35,8 @@
public abstract class VibratorManager {
private static final String TAG = "VibratorManager";
- private final String mPackageName;
+ /** @hide */
+ protected final String mPackageName;
/**
* @hide to prevent subclassing from outside of the framework
@@ -137,6 +138,21 @@
String reason, @Nullable VibrationAttributes attributes);
/**
+ * Performs a haptic feedback.
+ *
+ * @param constant the ID of the requested haptic feedback. Should be one of the constants
+ * defined in {@link HapticFeedbackConstants}.
+ * @param always {@code true} if the haptic feedback should be played regardless of the user
+ * vibration intensity settings applicable to the corresponding vibration.
+ * {@code false} otherwise.
+ * @param reason the reason for this haptic feedback.
+ * @hide
+ */
+ public void performHapticFeedback(int constant, boolean always, String reason) {
+ Log.w(TAG, "performHapticFeedback is not supported");
+ }
+
+ /**
* Turn all the vibrators off.
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
new file mode 100644
index 0000000..361e244
--- /dev/null
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.os.vibrator"
+
+flag {
+ namespace: "vibrator"
+ name: "use_vibrator_haptic_feedback"
+ description: "Enables performHapticFeedback to directly use the vibrator service instead of going through the window session"
+ bug: "295459081"
+}
\ No newline at end of file
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c7e5453..637770c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1307,9 +1307,9 @@
visibleFrame.intersect(mInsetsState.getDisplayFrame());
WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame,
null /* ignoringVisibilityState */, config.isScreenRound(),
- false /* alwaysConsumeSystemBars */, mLayout.softInputMode,
- mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type,
- config.windowConfiguration.getWindowingMode(), null /* idSideMap */);
+ mLayout.softInputMode, mLayout.flags, SYSTEM_UI_FLAG_VISIBLE,
+ mLayout.type, config.windowConfiguration.getActivityType(),
+ null /* idSideMap */);
if (!fixedSize) {
final Rect padding = mIWallpaperEngine.mDisplayPadding;
diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig
new file mode 100644
index 0000000..c67a21b
--- /dev/null
+++ b/core/java/android/text/flags/phrase_strict_fallback.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "phrase_strict_fallback"
+ namespace: "text"
+ description: "Feature flag for automatic fallback from phrase based line break to strict line break."
+ bug: "281970875"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 85f5395..5fe2aa1 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -156,6 +156,13 @@
public static final String SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS =
"settings_biometrics2_fingerprint";
+ /**
+ * Flag to enable/disable remote auth enrollment and settings
+ * @hide
+ */
+ public static final String SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS =
+ "settings_remoteauth_enrollment";
+
/** Flag to enable/disable entire page in Accessibility -> Hearing aids
* @hide
*/
@@ -248,6 +255,8 @@
DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false");
+ // TODO: b/298454866 Replace with Trunk Stable Feature Flag
+ DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index fabfed3..1ec7c41 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,7 +16,7 @@
package android.view;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.EventLogTags.IMF_IME_ANIM_CANCEL;
import static android.view.EventLogTags.IMF_IME_ANIM_FINISH;
import static android.view.EventLogTags.IMF_IME_ANIM_START;
@@ -39,6 +39,7 @@
import static android.view.InsetsState.ISIDE_RIGHT;
import static android.view.InsetsState.ISIDE_TOP;
import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
@@ -62,7 +63,6 @@
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
-import android.view.WindowManager.LayoutParams;
import android.view.animation.Interpolator;
import android.view.inputmethod.ImeTracker;
@@ -396,10 +396,9 @@
private Insets getInsetsFromState(InsetsState state, Rect frame,
@Nullable @InternalInsetsSide SparseIntArray idSideMap) {
return state.calculateInsets(frame, null /* ignoringVisibilityState */,
- false /* isScreenRound */, false /* alwaysConsumeSystemBars */,
- LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
+ false /* isScreenRound */, SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode */,
0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
- WINDOWING_MODE_UNDEFINED, idSideMap).getInsets(mTypes);
+ ACTIVITY_TYPE_UNDEFINED, idSideMap).getInsets(mTypes);
}
/** Computes the insets relative to the given frame. */
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8ec7d67..fb24211 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -652,7 +652,7 @@
private int mLastLegacySoftInputMode;
private int mLastLegacyWindowFlags;
private int mLastLegacySystemUiFlags;
- private int mLastWindowingMode;
+ private int mLastActivityType;
private boolean mStartingAnimation;
private int mCaptionInsetsHeight = 0;
private int mImeCaptionBarInsetsHeight = 0;
@@ -800,10 +800,10 @@
}
}
- WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
- mLastInsets.isRound(), false /* alwaysConsumeSystemBars */,
+ WindowInsets insets = state.calculateInsets(mFrame,
+ mState /* ignoringVisibilityState */, mLastInsets.isRound(),
mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
- mWindowType, mLastWindowingMode, null /* idSideMap */);
+ mWindowType, mLastActivityType, null /* idSideMap */);
mHost.dispatchWindowInsetsAnimationProgress(insets,
Collections.unmodifiableList(runningAnimations));
if (DEBUG) {
@@ -939,30 +939,29 @@
}
/**
- * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
- * int, android.util.SparseIntArray)
+ * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, int, int, int, int, int,
+ * android.util.SparseIntArray)
*/
@VisibleForTesting
- public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
- int windowType, int windowingMode, int legacySoftInputMode, int legacyWindowFlags,
- int legacySystemUiFlags) {
+ public WindowInsets calculateInsets(boolean isScreenRound, int windowType, int activityType,
+ int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags) {
mWindowType = windowType;
- mLastWindowingMode = windowingMode;
+ mLastActivityType = activityType;
mLastLegacySoftInputMode = legacySoftInputMode;
mLastLegacyWindowFlags = legacyWindowFlags;
mLastLegacySystemUiFlags = legacySystemUiFlags;
- mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/,
- isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags,
- legacySystemUiFlags, windowType, windowingMode, null /* idSideMap */);
+ mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState */,
+ isScreenRound, legacySoftInputMode, legacyWindowFlags,
+ legacySystemUiFlags, windowType, activityType, null /* idSideMap */);
return mLastInsets;
}
/**
* @see InsetsState#calculateVisibleInsets(Rect, int, int, int, int)
*/
- public Insets calculateVisibleInsets(int windowType, int windowingMode,
+ public Insets calculateVisibleInsets(int windowType, int activityType,
@SoftInputModeFlags int softInputMode, int windowFlags) {
- return mState.calculateVisibleInsets(mFrame, windowType, windowingMode, softInputMode,
+ return mState.calculateVisibleInsets(mFrame, windowType, activityType, softInputMode,
windowFlags);
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index af24140..59e0932 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
@@ -39,7 +40,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.ActivityType;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
@@ -136,9 +137,8 @@
* @return The calculated insets.
*/
public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState,
- boolean isScreenRound, boolean alwaysConsumeSystemBars,
- int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
- int windowType, @WindowConfiguration.WindowingMode int windowingMode,
+ boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags,
+ int legacySystemUiFlags, int windowType, @ActivityType int activityType,
@Nullable @InternalInsetsSide SparseIntArray idSideMap) {
Insets[] typeInsetsMap = new Insets[Type.SIZE];
Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
@@ -185,9 +185,8 @@
if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
compatInsetsTypes &= ~statusBars();
}
- if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) {
- // Clear all types but forceConsumingTypes.
- compatInsetsTypes &= forceConsumingTypes;
+ if (clearsCompatInsets(windowType, legacyWindowFlags, activityType, forceConsumingTypes)) {
+ compatInsetsTypes = 0;
}
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
@@ -295,26 +294,27 @@
return insets;
}
- public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode,
+ public Insets calculateVisibleInsets(Rect frame, int windowType, @ActivityType int activityType,
@SoftInputModeFlags int softInputMode, int windowFlags) {
- final boolean clearsCompatInsets = clearsCompatInsets(
- windowType, windowFlags, windowingMode);
final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST;
final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING
? systemBars() | ime()
: systemBars();
+ @InsetsType int forceConsumingTypes = 0;
Insets insets = Insets.NONE;
for (int i = mSources.size() - 1; i >= 0; i--) {
final InsetsSource source = mSources.valueAt(i);
if ((source.getType() & visibleInsetsTypes) == 0) {
continue;
}
- if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) {
- continue;
+ if (source.hasFlags(FLAG_FORCE_CONSUMING)) {
+ forceConsumingTypes |= source.getType();
}
insets = Insets.max(source.calculateVisibleInsets(frame), insets);
}
- return insets;
+ return clearsCompatInsets(windowType, windowFlags, activityType, forceConsumingTypes)
+ ? Insets.NONE
+ : insets;
}
/**
@@ -662,10 +662,15 @@
mSources.put(source.getId(), source);
}
- public static boolean clearsCompatInsets(int windowType, int windowFlags, int windowingMode) {
+ public static boolean clearsCompatInsets(int windowType, int windowFlags,
+ @ActivityType int activityType, @InsetsType int forceConsumingTypes) {
return (windowFlags & FLAG_LAYOUT_NO_LIMITS) != 0
+ // For compatibility reasons, this excludes the wallpaper, the system error windows,
+ // and the app windows while any system bar is forcibly consumed.
&& windowType != TYPE_WALLPAPER && windowType != TYPE_SYSTEM_ERROR
- && !WindowConfiguration.inMultiWindowMode(windowingMode);
+ // This ensures the app content won't be obscured by compat insets even if the app
+ // has FLAG_LAYOUT_NO_LIMITS.
+ && (forceConsumingTypes == 0 || activityType != ACTIVITY_TYPE_STANDARD);
}
public void dump(String prefix, PrintWriter pw) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9a4cb72..30fd2cf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -105,6 +105,8 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.Vibrator;
+import android.os.vibrator.Flags;
import android.sysprop.DisplayProperties;
import android.text.InputType;
import android.text.TextUtils;
@@ -5411,6 +5413,9 @@
*/
private PointerIcon mMousePointerIcon;
+ /** Vibrator for haptic feedback. */
+ private Vibrator mVibrator;
+
/**
* @hide
*/
@@ -27758,8 +27763,24 @@
&& !isHapticFeedbackEnabled()) {
return false;
}
- return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
- (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+
+ final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
+ if (Flags.useVibratorHapticFeedback()) {
+ if (!mAttachInfo.canPerformHapticFeedback()) {
+ return false;
+ }
+ getSystemVibrator().performHapticFeedback(
+ feedbackConstant, always, "View#performHapticFeedback");
+ return true;
+ }
+ return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always);
+ }
+
+ private Vibrator getSystemVibrator() {
+ if (mVibrator != null) {
+ return mVibrator;
+ }
+ return mVibrator = mContext.getSystemService(Vibrator.class);
}
/**
@@ -31239,6 +31260,11 @@
return events;
}
+ private boolean canPerformHapticFeedback() {
+ return mSession != null
+ && (mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) == 0;
+ }
+
@Nullable
ScrollCaptureInternal getScrollCaptureInternal() {
if (mScrollCaptureInternal != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0fda7e..60d964e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2848,16 +2848,15 @@
if (mLastWindowInsets == null || forceConstruct) {
final Configuration config = getConfiguration();
mLastWindowInsets = mInsetsController.calculateInsets(
- config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars,
- mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
- mWindowAttributes.softInputMode, mWindowAttributes.flags,
- (mWindowAttributes.systemUiVisibility
+ config.isScreenRound(), mWindowAttributes.type,
+ config.windowConfiguration.getActivityType(), mWindowAttributes.softInputMode,
+ mWindowAttributes.flags, (mWindowAttributes.systemUiVisibility
| mWindowAttributes.subtreeSystemUiVisibility));
mAttachInfo.mContentInsets.set(mLastWindowInsets.getSystemWindowInsets().toRect());
mAttachInfo.mStableInsets.set(mLastWindowInsets.getStableInsets().toRect());
mAttachInfo.mVisibleInsets.set(mInsetsController.calculateVisibleInsets(
- mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
+ mWindowAttributes.type, config.windowConfiguration.getActivityType(),
mWindowAttributes.softInputMode, mWindowAttributes.flags).toRect());
}
return mLastWindowInsets;
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
new file mode 100644
index 0000000..92d3408
--- /dev/null
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.view.inputmethod"
+
+flag {
+ name: "refactor_insets_controller"
+ namespace: "inputmethod"
+ description: "Feature flag for refactoring InsetsController and removing ImeInsetsSourceConsumer"
+ bug: "298172246"
+}
\ No newline at end of file
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 2858f0a..e32c8e5 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -16,7 +16,7 @@
package android.window;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -80,7 +80,7 @@
final Rect bounds;
final float density;
final boolean isScreenRound;
- final int windowingMode;
+ final int activityType;
synchronized (ResourcesManager.getInstance()) {
final Configuration config = mContext.getResources().getConfiguration();
final WindowConfiguration winConfig = config.windowConfiguration;
@@ -90,11 +90,11 @@
// as DisplayMetrics#density
density = config.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
isScreenRound = config.isScreenRound();
- windowingMode = winConfig.getWindowingMode();
+ activityType = winConfig.getActivityType();
}
final IBinder token = Context.getToken(mContext);
final Supplier<WindowInsets> insetsSupplier = () -> getWindowInsetsFromServerForDisplay(
- mContext.getDisplayId(), token, bounds, isScreenRound, windowingMode);
+ mContext.getDisplayId(), token, bounds, isScreenRound, activityType);
return new WindowMetrics(new Rect(bounds), insetsSupplier, density);
}
@@ -105,23 +105,22 @@
* @param token the token of Activity or WindowContext
* @param bounds the window bounds to calculate insets for
* @param isScreenRound if the display identified by displayId is round
- * @param windowingMode the windowing mode of the window to calculate insets for
+ * @param activityType the activity type of the window to calculate insets for
* @return WindowInsets calculated for the given window bounds, on the given display
*/
private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId, IBinder token,
- Rect bounds, boolean isScreenRound, int windowingMode) {
+ Rect bounds, boolean isScreenRound, int activityType) {
try {
final InsetsState insetsState = new InsetsState();
- final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService()
- .getWindowInsets(displayId, token, insetsState);
+ WindowManagerGlobal.getWindowManagerService().getWindowInsets(
+ displayId, token, insetsState);
final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
if (overrideInvScale != 1f) {
insetsState.scale(overrideInvScale);
}
return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */,
- isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
- 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
- WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
+ isScreenRound, SOFT_INPUT_ADJUST_NOTHING, 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
+ WindowManager.LayoutParams.INVALID_WINDOW_TYPE, activityType,
null /* idSideMap */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -157,7 +156,7 @@
currentDisplayInfo.displayId, null /* token */,
new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
currentDisplayInfo.getNaturalHeight()), isScreenRound,
- WINDOWING_MODE_FULLSCREEN);
+ ACTIVITY_TYPE_UNDEFINED);
// Set the hardware-provided insets.
windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
currentDisplayInfo.roundedCorners)
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 560e41b..b8d251f 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -8,3 +8,10 @@
description: "Whether the feature to sync different window-related config updates is enabled"
bug: "260873529"
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "activity_embedding_overlay_presentation_flag"
+ description: "Whether the overlay presentation feature is enabled"
+ bug: "243518738"
+}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index bb86801..86ca077 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1113,15 +1113,14 @@
if (insets != null) {
mLastForceConsumingTypes = insets.getForceConsumingTypes();
- @InsetsType int compatInsetsTypes =
+ final boolean clearsCompatInsets = clearsCompatInsets(attrs.type, attrs.flags,
+ getResources().getConfiguration().windowConfiguration.getActivityType(),
+ mLastForceConsumingTypes);
+ final @InsetsType int compatInsetsTypes =
WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
- if (clearsCompatInsets(attrs.type, attrs.flags,
- getResources().getConfiguration().windowConfiguration.getWindowingMode())) {
- compatInsetsTypes &= mLastForceConsumingTypes;
- }
final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
WindowInsets.Type.systemBars());
- final Insets systemInsets = compatInsetsTypes == 0
+ final Insets systemInsets = clearsCompatInsets
? Insets.NONE
: Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets);
mLastTopInset = systemInsets.top;
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index ccae67f..6440cc3 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2071,10 +2071,67 @@
mixerBehavior);
}
-static jint convertAudioMixToNative(JNIEnv *env,
- AudioMix *nAudioMix,
- const jobject jAudioMix)
-{
+static jint convertAudioMixingRuleToNative(JNIEnv *env, const jobject audioMixingRule,
+ std::vector<AudioMixMatchCriterion> *nCriteria) {
+ jobject jRuleCriteria = env->GetObjectField(audioMixingRule, gAudioMixingRuleFields.mCriteria);
+
+ jobjectArray jCriteria = static_cast<jobjectArray>(
+ env->CallObjectMethod(jRuleCriteria, gArrayListMethods.toArray));
+ env->DeleteLocalRef(jRuleCriteria);
+
+ jint numCriteria = env->GetArrayLength(jCriteria);
+ if (numCriteria > MAX_CRITERIA_PER_MIX) {
+ numCriteria = MAX_CRITERIA_PER_MIX;
+ }
+
+ nCriteria->resize(numCriteria);
+ for (jint i = 0; i < numCriteria; i++) {
+ AudioMixMatchCriterion &nCriterion = (*nCriteria)[i];
+
+ jobject jCriterion = env->GetObjectArrayElement(jCriteria, i);
+
+ nCriterion.mRule = env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mRule);
+
+ const uint32_t match_rule = nCriterion.mRule & ~RULE_EXCLUSION_MASK;
+ switch (match_rule) {
+ case RULE_MATCH_UID:
+ nCriterion.mValue.mUid =
+ env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+ break;
+ case RULE_MATCH_USERID:
+ nCriterion.mValue.mUserId =
+ env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+ break;
+ case RULE_MATCH_AUDIO_SESSION_ID: {
+ jint jAudioSessionId =
+ env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+ nCriterion.mValue.mAudioSessionId = static_cast<audio_session_t>(jAudioSessionId);
+ } break;
+ case RULE_MATCH_ATTRIBUTE_USAGE:
+ case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
+ jobject jAttributes =
+ env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
+
+ auto paa = JNIAudioAttributeHelper::makeUnique();
+ jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get());
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+ if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) {
+ nCriterion.mValue.mUsage = paa->usage;
+ } else {
+ nCriterion.mValue.mSource = paa->source;
+ }
+ env->DeleteLocalRef(jAttributes);
+ } break;
+ }
+ env->DeleteLocalRef(jCriterion);
+ }
+ env->DeleteLocalRef(jCriteria);
+ return AUDIO_JAVA_SUCCESS;
+}
+
+static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobject jAudioMix) {
nAudioMix->mMixType = env->GetIntField(jAudioMix, gAudioMixFields.mMixType);
nAudioMix->mRouteFlags = env->GetIntField(jAudioMix, gAudioMixFields.mRouteFlags);
nAudioMix->mDeviceType =
@@ -2094,69 +2151,16 @@
env->DeleteLocalRef(jFormat);
jobject jRule = env->GetObjectField(jAudioMix, gAudioMixFields.mRule);
- jobject jRuleCriteria = env->GetObjectField(jRule, gAudioMixingRuleFields.mCriteria);
nAudioMix->mAllowPrivilegedMediaPlaybackCapture =
env->GetBooleanField(jRule, gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture);
nAudioMix->mVoiceCommunicationCaptureAllowed =
env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
+
+ jint status = convertAudioMixingRuleToNative(env, jRule, &(nAudioMix->mCriteria));
+
env->DeleteLocalRef(jRule);
- jobjectArray jCriteria = static_cast<jobjectArray>(
- env->CallObjectMethod(jRuleCriteria, gArrayListMethods.toArray));
- env->DeleteLocalRef(jRuleCriteria);
- jint numCriteria = env->GetArrayLength(jCriteria);
- if (numCriteria > MAX_CRITERIA_PER_MIX) {
- numCriteria = MAX_CRITERIA_PER_MIX;
- }
-
- for (jint i = 0; i < numCriteria; i++) {
- AudioMixMatchCriterion nCriterion;
-
- jobject jCriterion = env->GetObjectArrayElement(jCriteria, i);
-
- nCriterion.mRule = env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mRule);
-
- const uint32_t match_rule = nCriterion.mRule & ~RULE_EXCLUSION_MASK;
- switch (match_rule) {
- case RULE_MATCH_UID:
- nCriterion.mValue.mUid = env->GetIntField(jCriterion,
- gAudioMixMatchCriterionFields.mIntProp);
- break;
- case RULE_MATCH_USERID:
- nCriterion.mValue.mUserId =
- env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
- break;
- case RULE_MATCH_AUDIO_SESSION_ID: {
- jint jAudioSessionId =
- env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
- nCriterion.mValue.mAudioSessionId = static_cast<audio_session_t>(jAudioSessionId);
- } break;
- case RULE_MATCH_ATTRIBUTE_USAGE:
- case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
- jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
-
- auto paa = JNIAudioAttributeHelper::makeUnique();
- jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get());
- if (jStatus != AUDIO_JAVA_SUCCESS) {
- return jStatus;
- }
- if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) {
- nCriterion.mValue.mUsage = paa->usage;
- } else {
- nCriterion.mValue.mSource = paa->source;
- }
- env->DeleteLocalRef(jAttributes);
- }
- break;
- }
-
- nAudioMix->mCriteria.push_back(nCriterion);
- env->DeleteLocalRef(jCriterion);
- }
-
- env->DeleteLocalRef(jCriteria);
-
- return AUDIO_JAVA_SUCCESS;
+ return status;
}
static jint
@@ -2200,6 +2204,45 @@
return nativeToJavaStatus(status);
}
+static jint android_media_AudioSystem_updatePolicyMixes(JNIEnv *env, jobject clazz,
+ jobjectArray mixes,
+ jobjectArray updatedMixingRules) {
+ if (mixes == nullptr || updatedMixingRules == nullptr) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ jsize updatesCount = env->GetArrayLength(mixes);
+ if (updatesCount == 0 || updatesCount != env->GetArrayLength(updatedMixingRules)) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ std::vector<std::pair<AudioMix, std::vector<AudioMixMatchCriterion>>> updates(updatesCount);
+ for (int i = 0; i < updatesCount; i++) {
+ jobject jAudioMix = env->GetObjectArrayElement(mixes, i);
+ jobject jAudioMixingRule = env->GetObjectArrayElement(updatedMixingRules, i);
+ if (!env->IsInstanceOf(jAudioMix, gAudioMixClass) ||
+ !env->IsInstanceOf(jAudioMixingRule, gAudioMixingRuleClass)) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+
+ jint ret;
+ if ((ret = convertAudioMixToNative(env, &updates[i].first, jAudioMix)) !=
+ AUDIO_JAVA_SUCCESS) {
+ return ret;
+ }
+ if ((ret = convertAudioMixingRuleToNative(env, jAudioMixingRule, &updates[i].second)) !=
+ AUDIO_JAVA_SUCCESS) {
+ return ret;
+ }
+ }
+
+ ALOGV("AudioSystem::updatePolicyMixes numMixes %d", updatesCount);
+ int status = AudioSystem::updatePolicyMixes(updates);
+ ALOGV("AudioSystem::updatePolicyMixes returned %d", status);
+
+ return nativeToJavaStatus(status);
+}
+
static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobject clazz,
jint uid, jintArray deviceTypes, jobjectArray deviceAddresses) {
AudioDeviceTypeAddrVector deviceVector;
@@ -3158,6 +3201,10 @@
MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
android_media_AudioSystem_registerPolicyMixes),
+ MAKE_JNI_NATIVE_METHOD("updatePolicyMixes",
+ "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/"
+ "AudioMixingRule;)I",
+ android_media_AudioSystem_updatePolicyMixes),
MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
android_media_AudioSystem_setUidDeviceAffinities),
MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities),
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index d1a4935..37d2fa0 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -85,14 +85,18 @@
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
<Button
android:id="@+id/autofill_dialog_no"
android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_marginTop="6dp"
- android:layout_marginBottom="6dp"
+ android:layout_height="40dp"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
+ android:minWidth="0dp"
style="?android:attr/borderlessButtonStyle"
android:text="@string/autofill_save_no">
</Button>
@@ -107,9 +111,8 @@
<Button
android:id="@+id/autofill_dialog_yes"
android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_marginTop="6dp"
- android:layout_marginBottom="6dp"
+ android:layout_height="40dp"
+ android:minWidth="0dp"
style="@style/AutofillHalfSheetTonalButton"
android:text="@string/autofill_save_yes"
android:visibility="gone" >
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 85529d6..bed19a8 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -72,14 +72,18 @@
android:layout_marginTop="32dp"
android:layout_marginBottom="18dp"
android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
<Button
android:id="@+id/autofill_save_no"
android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_marginTop="6dp"
- android:layout_marginBottom="6dp"
+ android:layout_height="40dp"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
+ android:minWidth="0dp"
style="?android:attr/borderlessButtonStyle"
android:text="@string/autofill_save_no">
</Button>
@@ -94,9 +98,8 @@
<Button
android:id="@+id/autofill_save_yes"
android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_marginTop="6dp"
- android:layout_marginBottom="6dp"
+ android:layout_height="40dp"
+ android:minWidth="0dp"
style="@style/AutofillHalfSheetTonalButton"
android:text="@string/autofill_save_yes">
</Button>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a72f779..03480e4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1185,6 +1185,9 @@
-->
<integer name="config_shortPressOnSleepBehavior">0</integer>
+ <!-- Whether to silence telephony ringer on sleep key event -->
+ <bool name="config_silenceRingerOnSleepKey">false</bool>
+
<!-- Control the behavior when the user long presses the stem primary button.
Stem primary button is only used on watch form factor. If a device is not
a watch, setting this config is no-op.
@@ -6182,6 +6185,8 @@
<!-- Default value for Settings.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED -->
<bool name="config_searchPressHoldNavHandleEnabledDefault">true</bool>
+ <!-- Default value for Settings.ASSIST_LONG_PRESS_HOME_ENABLED for search overlay -->
+ <bool name="config_searchLongPressHomeEnabledDefault">true</bool>
<!-- The maximum byte size of the information contained in the bundle of
HotwordDetectedResult. -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 164f713..13d04e5 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1500,12 +1500,8 @@
<item name="fontFamily">google-sans-text-medium</item>
<item name="textStyle">normal</item>
<item name="textAllCaps">false</item>
- <item name="layout_marginTop">6dp</item>
- <item name="layout_marginBottom">6dp</item>
- <item name="paddingStart">16dp</item>
- <item name="paddingEnd">16dp</item>
- <item name="paddingTop">8dp</item>
- <item name="paddingBottom">8dp</item>
+ <item name="paddingStart">24dp</item>
+ <item name="paddingEnd">24dp</item>
</style>
<!-- @hide Tonal button for Autofill half screen dialog -->
<style name="AutofillHalfSheetTonalButton" parent="AutofillHalfSheetButton">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ee0563b..7ea5974 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -464,6 +464,7 @@
<java-symbol type="integer" name="config_toastDefaultGravity" />
<java-symbol type="integer" name="config_triplePressOnPowerBehavior" />
<java-symbol type="integer" name="config_shortPressOnSleepBehavior" />
+ <java-symbol type="bool" name="config_silenceRingerOnSleepKey" />
<java-symbol type="integer" name="config_longPressOnStemPrimaryBehavior" />
<java-symbol type="integer" name="config_shortPressOnStemPrimaryBehavior" />
<java-symbol type="string" name="config_primaryShortPressTargetActivity" />
@@ -4914,6 +4915,7 @@
<java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" />
<java-symbol type="bool" name="config_searchPressHoldNavHandleEnabledDefault" />
+ <java-symbol type="bool" name="config_searchLongPressHomeEnabledDefault" />
<java-symbol type="integer" name="config_hotwordDetectedResultMaxBundleSize" />
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index 0676f89..aaaa3c7 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -241,7 +241,8 @@
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
/* includePowerModels */ true,
- /* includeProcessStats */true)
+ /* includeProcessStats */ true,
+ /* minConsumedPowerThreshold */ 0)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
.setDischargeDurationMs(1234)
@@ -325,7 +326,7 @@
@Test
public void testLargeAtomTruncated() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[0], true, false);
+ new BatteryUsageStats.Builder(new String[0], true, false, 0);
// If not truncated, this BatteryUsageStats object would generate a proto buffer
// significantly larger than 50 Kb
for (int i = 0; i < 3000; i++) {
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index f45db23..8c93fbb 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -16,7 +16,7 @@
package android.view;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsSource.ID_IME;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -81,8 +81,7 @@
Insets.of(10, 10, 10, 10), rect, rect, rect, rect));
mController.calculateInsets(
false,
- false,
- TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
SOFT_INPUT_ADJUST_RESIZE, 0, 0);
mImeConsumer = mController.getImeSourceConsumer();
});
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index b8f0d5c..1568174 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,7 +16,7 @@
package android.view;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
@@ -171,8 +171,7 @@
mController.onStateChanged(state);
mController.calculateInsets(
false,
- false,
- TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
SOFT_INPUT_ADJUST_RESIZE, 0, 0);
mController.onFrameChanged(new Rect(0, 0, 100, 100));
});
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index b06cd39..906d84e 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -16,8 +16,9 @@
package android.view;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ISIDE_BOTTOM;
import static android.view.InsetsState.ISIDE_TOP;
@@ -101,7 +102,7 @@
.setVisible(true);
SparseIntArray typeSideMap = new SparseIntArray();
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
typeSideMap);
assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
@@ -120,7 +121,7 @@
.setFrame(new Rect(0, 100, 100, 300))
.setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
null);
assertEquals(100, insets.getStableInsetBottom());
assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(systemBars()));
@@ -139,7 +140,7 @@
.setFrame(new Rect(80, 0, 100, 300))
.setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ 0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -154,7 +155,7 @@
.setFrame(new Rect(80, 0, 100, 300))
.setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ 0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
@@ -169,7 +170,7 @@
.setFrame(new Rect(0, 200, 100, 300))
.setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
null);
assertEquals(0, insets.getSystemWindowInsetBottom());
assertEquals(100, insets.getInsets(ime()).bottom);
@@ -185,12 +186,12 @@
.setFrame(new Rect(0, 200, 100, 300))
.setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
- WINDOWING_MODE_UNDEFINED, null);
+ SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
+ ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(100, insets.getSystemWindowInsetTop());
- insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
+ insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
- WINDOWING_MODE_UNDEFINED, null);
+ ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(0, insets.getSystemWindowInsetTop());
}
@@ -200,12 +201,12 @@
.setFrame(new Rect(0, 0, 100, 100))
.setVisible(false);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
- TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
+ TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(0, insets.getSystemWindowInsetTop());
- insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
+ insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
- WINDOWING_MODE_UNDEFINED, null);
+ ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(0, insets.getSystemWindowInsetTop());
}
@@ -213,22 +214,23 @@
public void testCalculateInsets_flagLayoutNoLimits() {
mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
.setFrame(new Rect(0, 0, 100, 100))
- .setVisible(true);
+ .setVisible(true)
+ .setFlags(FLAG_FORCE_CONSUMING);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
- 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+ 0 /* legacySystemUiFlags */, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(0, insets.getSystemWindowInsetTop());
insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
- 0 /* legacySystemUiFlags */, TYPE_SYSTEM_ERROR, WINDOWING_MODE_UNDEFINED, null);
+ SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+ 0 /* legacySystemUiFlags */, TYPE_SYSTEM_ERROR, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(100, insets.getSystemWindowInsetTop());
insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
- 0 /* legacySystemUiFlags */, TYPE_WALLPAPER, WINDOWING_MODE_UNDEFINED, null);
+ SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+ 0 /* legacySystemUiFlags */, TYPE_WALLPAPER, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(100, insets.getSystemWindowInsetTop());
insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
- 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_FREEFORM, null);
+ SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
+ 0 /* legacySystemUiFlags */, TYPE_APPLICATION, ACTIVITY_TYPE_STANDARD, null);
assertEquals(100, insets.getSystemWindowInsetTop());
}
@@ -243,7 +245,7 @@
.setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 100, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ new Rect(0, 0, 100, 400), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
assertEquals(Insets.of(0, 300, 0, 0), visibleInsets);
}
@@ -255,7 +257,7 @@
.setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 150, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ new Rect(0, 0, 150, 400), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
assertEquals(Insets.of(0, 300, 0, 0), visibleInsets);
}
@@ -269,7 +271,7 @@
.setFrame(new Rect(80, 0, 100, 300))
.setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ 0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -284,7 +286,7 @@
.setFrame(new Rect(80, 0, 100, 300))
.setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
- false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ 0, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars()));
@@ -292,11 +294,11 @@
@Test
public void testCalculateInsets_emptyIme() {
- WindowInsets insets1 = mState.calculateInsets(new Rect(), null, false, false,
- SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ WindowInsets insets1 = mState.calculateInsets(new Rect(), null, false,
+ SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
mState.getOrCreateSource(ID_IME, ime());
- WindowInsets insets2 = mState.calculateInsets(new Rect(), null, false, false,
- SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ WindowInsets insets2 = mState.calculateInsets(new Rect(), null, false,
+ SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(Insets.NONE, insets1.getInsets(ime()));
assertEquals(Insets.NONE, insets2.getInsets(ime()));
assertEquals(insets1, insets2);
@@ -311,8 +313,8 @@
.setFrame(new Rect(0, 200, 100, 300))
.setVisible(true);
mState.removeSource(ID_IME);
- WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
- SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED, null);
assertEquals(0, insets.getSystemWindowInsetBottom());
}
@@ -527,7 +529,7 @@
.setFrame(new Rect(0, 100, 100, 300))
.setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ new Rect(0, 0, 100, 300), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
SOFT_INPUT_ADJUST_PAN, 0 /* windowFlags */);
assertEquals(Insets.of(0, 100, 0, 100), visibleInsets);
}
@@ -546,7 +548,7 @@
.setFrame(new Rect(0, 100, 100, 300))
.setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ new Rect(0, 0, 100, 300), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
assertEquals(Insets.of(0, 100, 0, 0), visibleInsets);
}
@@ -565,7 +567,7 @@
.setFrame(new Rect(0, 100, 100, 300))
.setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
- new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ new Rect(0, 0, 100, 300), TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
SOFT_INPUT_ADJUST_PAN, FLAG_LAYOUT_NO_LIMITS);
assertEquals(Insets.NONE, visibleInsets);
}
@@ -599,8 +601,8 @@
new Rect(0, 0, 1, 2),
new Rect(197, 296, 200, 300),
new Rect(197, 296, 200, 300)));
- DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false, false,
- SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
+ DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false,
+ SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
new SparseIntArray()).getDisplayCutout();
assertEquals(0, cutout.getSafeInsetLeft());
assertEquals(1, cutout.getSafeInsetTop());
@@ -625,8 +627,8 @@
new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380),
new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)));
WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false,
- false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
- WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+ SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+ ACTIVITY_TYPE_UNDEFINED, new SparseIntArray());
assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8),
windowInsets.getRoundedCorner(POSITION_TOP_LEFT));
assertEquals(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8),
@@ -642,8 +644,8 @@
mState.setDisplayFrame(new Rect(0, 0, 200, 400));
mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false));
WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false,
- false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
- WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+ SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+ ACTIVITY_TYPE_UNDEFINED, new SparseIntArray());
final DisplayShape expect =
DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 72702e7..b828aac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Slog;
import android.view.IDisplayChangeWindowCallback;
import android.view.IDisplayChangeWindowController;
@@ -40,6 +41,7 @@
*/
public class DisplayChangeController {
private static final String TAG = DisplayChangeController.class.getSimpleName();
+ private static final String HANDLE_DISPLAY_CHANGE_TRACE_TAG = "HandleRemoteDisplayChange";
private final ShellExecutor mMainExecutor;
private final IWindowManager mWmService;
@@ -81,9 +83,15 @@
/** Query all listeners for changes that should happen on display change. */
void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId,
int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginSection("dispatchOnDisplayChange");
+ }
for (OnDisplayChangingListener c : mDisplayChangeListener) {
c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endSection();
+ }
}
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -94,6 +102,10 @@
callback.continueDisplayChange(t);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to continue handling display change", e);
+ } finally {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
}
}
@@ -103,6 +115,9 @@
@Override
public void onDisplayChange(int displayId, int fromRotation, int toRotation,
DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
mMainExecutor.execute(() -> DisplayChangeController.this
.onDisplayChange(displayId, fromRotation, toRotation,
newDisplayAreaInfo, callback));
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 3f013de..61b5fd5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -29,6 +29,7 @@
import android.media.audio.common.AidlConversion;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioProductStrategy;
import android.os.Build;
import android.os.IBinder;
@@ -1955,6 +1956,11 @@
/** @hide */
public static native int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register);
+ /** @hide */
+ public static native int updatePolicyMixes(
+ AudioMix[] mixes,
+ AudioMixingRule[] updatedMixingRules);
+
/** @hide see AudioPolicy.setUidDeviceAffinities() */
public static native int setUidDeviceAffinities(int uid, @NonNull int[] types,
@NonNull String[] addresses);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e45ef40..0e7718b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -54,6 +54,8 @@
import android.media.PlayerBase;
import android.media.VolumeInfo;
import android.media.VolumePolicy;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
@@ -356,6 +358,11 @@
int removeMixForPolicy(in AudioPolicyConfig policyConfig, in IAudioPolicyCallback pcb);
+ @EnforcePermission("MODIFY_AUDIO_ROUTING")
+ int updateMixingRulesForPolicy(in AudioMix[] mixesToUpdate,
+ in AudioMixingRule[] updatedMixingRules,
+ in IAudioPolicyCallback pcb);
+
int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb);
void setVolumePolicy(in VolumePolicy policy);
diff --git a/media/java/android/media/audiopolicy/AudioMix.aidl b/media/java/android/media/audiopolicy/AudioMix.aidl
new file mode 100644
index 0000000..d17a644
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMix.aidl
@@ -0,0 +1,3 @@
+package android.media.audiopolicy;
+
+parcelable AudioMix;
\ No newline at end of file
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index d0270d3..48b4766 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -21,12 +21,15 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioSystem;
import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,12 +41,12 @@
* @hide
*/
@SystemApi
-public class AudioMix {
+public class AudioMix implements Parcelable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private AudioMixingRule mRule;
+ private @NonNull AudioMixingRule mRule;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private AudioFormat mFormat;
+ private @NonNull AudioFormat mFormat;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int mRouteFlags;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -54,7 +57,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
int mCallbackFlags;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- String mDeviceAddress;
+ @NonNull String mDeviceAddress;
// initialized in constructor, read by AudioPolicyConfig
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -63,10 +66,11 @@
/**
* All parameters are guaranteed valid through the Builder.
*/
- private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags,
- int deviceType, String deviceAddress) {
- mRule = rule;
- mFormat = format;
+ private AudioMix(@NonNull AudioMixingRule rule, @NonNull AudioFormat format,
+ int routeFlags, int callbackFlags,
+ int deviceType, @Nullable String deviceAddress) {
+ mRule = Objects.requireNonNull(rule);
+ mFormat = Objects.requireNonNull(format);
mRouteFlags = routeFlags;
mMixType = rule.getTargetMixType();
mCallbackFlags = callbackFlags;
@@ -187,6 +191,15 @@
}
/** @hide */
+ public void setAudioMixingRule(@NonNull AudioMixingRule rule) {
+ if (mRule.getTargetMixType() != rule.getTargetMixType()) {
+ throw new UnsupportedOperationException(
+ "Target mix role of updated rule doesn't match the mix role of the AudioMix");
+ }
+ mRule = Objects.requireNonNull(rule);
+ }
+
+ /** @hide */
public String getRegistration() {
return mDeviceAddress;
}
@@ -269,6 +282,49 @@
return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // write mix route flags
+ dest.writeInt(mRouteFlags);
+ // write callback flags
+ dest.writeInt(mCallbackFlags);
+ // write device information
+ dest.writeInt(mDeviceSystemType);
+ dest.writeString8(mDeviceAddress);
+ mFormat.writeToParcel(dest, flags);
+ mRule.writeToParcel(dest, flags);
+ }
+
+ public static final @NonNull Parcelable.Creator<AudioMix> CREATOR = new Parcelable.Creator<>() {
+ /**
+ * Rebuilds an AudioMix previously stored with writeToParcel().
+ *
+ * @param p Parcel object to read the AudioMix from
+ * @return a new AudioMix created from the data in the parcel
+ */
+ public AudioMix createFromParcel(Parcel p) {
+ final AudioMix.Builder mixBuilder = new AudioMix.Builder();
+ // read mix route flags
+ mixBuilder.setRouteFlags(p.readInt());
+ // read callback flags
+ mixBuilder.setCallbackFlags(p.readInt());
+ // read device information
+ mixBuilder.setDevice(p.readInt(), p.readString8());
+ mixBuilder.setFormat(AudioFormat.CREATOR.createFromParcel(p));
+ mixBuilder.setMixingRule(AudioMixingRule.CREATOR.createFromParcel(p));
+ return mixBuilder.build();
+ }
+
+ public AudioMix[] newArray(int size) {
+ return new AudioMix[size];
+ }
+ };
+
/** @hide */
@IntDef(flag = true,
value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
@@ -298,7 +354,7 @@
* @param rule a non-null {@link AudioMixingRule} instance.
* @throws IllegalArgumentException
*/
- public Builder(AudioMixingRule rule)
+ public Builder(@NonNull AudioMixingRule rule)
throws IllegalArgumentException {
if (rule == null) {
throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -313,7 +369,7 @@
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
- Builder setMixingRule(AudioMixingRule rule)
+ Builder setMixingRule(@NonNull AudioMixingRule rule)
throws IllegalArgumentException {
if (rule == null) {
throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -358,7 +414,7 @@
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
- public Builder setFormat(AudioFormat format)
+ public Builder setFormat(@NonNull AudioFormat format)
throws IllegalArgumentException {
if (format == null) {
throw new IllegalArgumentException("Illegal null AudioFormat argument");
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.aidl b/media/java/android/media/audiopolicy/AudioMixingRule.aidl
new file mode 100644
index 0000000..5c06538
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.aidl
@@ -0,0 +1,3 @@
+package android.media.audiopolicy;
+
+parcelable AudioMixingRule;
\ No newline at end of file
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 9c0b825f..e5debb8 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -26,8 +26,11 @@
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Collection;
@@ -50,7 +53,7 @@
* </pre>
*/
@SystemApi
-public class AudioMixingRule {
+public class AudioMixingRule implements Parcelable {
private AudioMixingRule(int mixType, Collection<AudioMixMatchCriterion> criteria,
boolean allowPrivilegedMediaPlaybackCapture,
@@ -130,7 +133,7 @@
RULE_EXCLUSION_MASK | RULE_MATCH_AUDIO_SESSION_ID;
/** @hide */
- public static final class AudioMixMatchCriterion {
+ public static final class AudioMixMatchCriterion implements Parcelable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
final AudioAttributes mAttr;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -139,18 +142,44 @@
final int mRule;
/** input parameters must be valid */
- AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
+ @VisibleForTesting
+ public AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
mAttr = attributes;
mIntProp = Integer.MIN_VALUE;
mRule = rule;
}
/** input parameters must be valid */
- AudioMixMatchCriterion(Integer intProp, int rule) {
+ @VisibleForTesting
+ public AudioMixMatchCriterion(Integer intProp, int rule) {
mAttr = null;
mIntProp = intProp.intValue();
mRule = rule;
}
+ private AudioMixMatchCriterion(@NonNull Parcel in) {
+ Objects.requireNonNull(in);
+ mRule = in.readInt();
+ final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
+ switch (match_rule) {
+ case RULE_MATCH_ATTRIBUTE_USAGE:
+ case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+ mAttr = AudioAttributes.CREATOR.createFromParcel(in);
+ mIntProp = Integer.MIN_VALUE;
+ break;
+ case RULE_MATCH_UID:
+ case RULE_MATCH_USERID:
+ case RULE_MATCH_AUDIO_SESSION_ID:
+ mIntProp = in.readInt();
+ mAttr = null;
+ break;
+ default:
+ // assume there was in int value to read as for now they come in pair
+ in.readInt();
+ throw new IllegalArgumentException(
+ "Illegal rule value " + mRule + " in parcel");
+ }
+ }
+
@Override
public int hashCode() {
return Objects.hash(mAttr, mIntProp, mRule);
@@ -170,7 +199,13 @@
&& Objects.equals(mAttr, other.mAttr);
}
- void writeToParcel(Parcel dest) {
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mRule);
final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
switch (match_rule) {
@@ -190,6 +225,22 @@
}
}
+ public static final @NonNull Parcelable.Creator<AudioMixMatchCriterion> CREATOR =
+ new Parcelable.Creator<>() {
+ /**
+ * Rebuilds an AudioMixMatchCriterion previously stored with writeToParcel().
+ *
+ * @param p Parcel object to read the AudioMix from
+ * @return a new AudioMixMatchCriterion created from the data in the parcel
+ */
+ public AudioMixMatchCriterion createFromParcel(Parcel p) {
+ return new AudioMixMatchCriterion(p);
+ }
+ public AudioMixMatchCriterion[] newArray(int size) {
+ return new AudioMixMatchCriterion[size];
+ }
+ };
+
public AudioAttributes getAudioAttributes() { return mAttr; }
public int getIntProp() { return mIntProp; }
public int getRule() { return mRule; }
@@ -605,13 +656,14 @@
if (!(property instanceof AudioAttributes)) {
throw new IllegalArgumentException("Invalid AudioAttributes argument");
}
- return addRuleInternal((AudioAttributes) property, null, rule);
+ return addRuleInternal(
+ new AudioMixMatchCriterion((AudioAttributes) property, rule));
} else {
// implies integer match rule
if (!(property instanceof Integer)) {
throw new IllegalArgumentException("Invalid Integer argument");
}
- return addRuleInternal(null, (Integer) property, rule);
+ return addRuleInternal(new AudioMixMatchCriterion((Integer) property, rule));
}
}
@@ -636,12 +688,13 @@
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
- private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
+ private Builder addRuleInternal(AudioMixMatchCriterion criterion)
throws IllegalArgumentException {
// If mix type is invalid and added rule is valid only for the players / recorders,
// adjust the mix type accordingly.
// Otherwise, if the mix type was already deduced or set explicitly, verify the rule
// is valid for the mix type.
+ final int rule = criterion.mRule;
if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
if (isPlayerRule(rule)) {
mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
@@ -655,51 +708,16 @@
}
synchronized (mCriteria) {
int oppositeRule = rule ^ RULE_EXCLUSION_MASK;
- if (mCriteria.stream().anyMatch(criterion -> criterion.mRule == oppositeRule)) {
+ if (mCriteria.stream().anyMatch(
+ otherCriterion -> otherCriterion.mRule == oppositeRule)) {
throw new IllegalArgumentException("AudioMixingRule cannot contain RULE_MATCH_*"
+ " and RULE_EXCLUDE_* for the same dimension.");
}
- int ruleWithoutExclusion = rule & ~RULE_EXCLUSION_MASK;
- switch (ruleWithoutExclusion) {
- case RULE_MATCH_ATTRIBUTE_USAGE:
- case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
- mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
- break;
- case RULE_MATCH_UID:
- case RULE_MATCH_USERID:
- case RULE_MATCH_AUDIO_SESSION_ID:
- mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
- break;
- default:
- throw new IllegalStateException("Unreachable code in addRuleInternal()");
- }
+ mCriteria.add(criterion);
}
return this;
}
- Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
- final int rule = in.readInt();
- final int match_rule = rule & ~RULE_EXCLUSION_MASK;
- AudioAttributes attr = null;
- Integer intProp = null;
- switch (match_rule) {
- case RULE_MATCH_ATTRIBUTE_USAGE:
- case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
- attr = AudioAttributes.CREATOR.createFromParcel(in);
- break;
- case RULE_MATCH_UID:
- case RULE_MATCH_USERID:
- case RULE_MATCH_AUDIO_SESSION_ID:
- intProp = new Integer(in.readInt());
- break;
- default:
- // assume there was in int value to read as for now they come in pair
- in.readInt();
- throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
- }
- return addRuleInternal(attr, intProp, rule);
- }
-
/**
* Combines all of the matching and exclusion rules that have been set and return a new
* {@link AudioMixingRule} object.
@@ -717,4 +735,52 @@
mVoiceCommunicationCaptureAllowed);
}
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // write opt-out respect
+ dest.writeBoolean(mAllowPrivilegedPlaybackCapture);
+ // write voice communication capture allowed flag
+ dest.writeBoolean(mVoiceCommunicationCaptureAllowed);
+ // write specified mix type
+ dest.writeInt(mTargetMixType);
+ // write mix rules
+ dest.writeInt(mCriteria.size());
+ for (AudioMixingRule.AudioMixMatchCriterion criterion : mCriteria) {
+ criterion.writeToParcel(dest, flags);
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<AudioMixingRule> CREATOR =
+ new Parcelable.Creator<>() {
+
+ @Override
+ public AudioMixingRule createFromParcel(Parcel source) {
+ AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
+ // read opt-out respect
+ ruleBuilder.allowPrivilegedPlaybackCapture(source.readBoolean());
+ // read voice capture allowed flag
+ ruleBuilder.voiceCommunicationCaptureAllowed(source.readBoolean());
+ // read specified mix type
+ ruleBuilder.setTargetMixRole(source.readInt());
+ // read mix rules
+ int nbRules = source.readInt();
+ for (int j = 0; j < nbRules; j++) {
+ // read the matching rules
+ ruleBuilder.addRuleInternal(
+ AudioMixMatchCriterion.CREATOR.createFromParcel(source));
+ }
+ return ruleBuilder.build();
+ }
+
+ @Override
+ public AudioMixingRule[] newArray(int size) {
+ return new AudioMixingRule[size];
+ }
+ };
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 3e5de82..e9a6ed4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -44,6 +44,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -408,6 +409,39 @@
}
/**
+ * Update {@link AudioMixingRule}-s of already registered {@link AudioMix}-es.
+ *
+ * @param mixingRuleUpdates - {@link List} of {@link Pair}-s, each pair containing
+ * {@link AudioMix} to update and its new corresponding {@link AudioMixingRule}.
+ *
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public int updateMixingRules(
+ @NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) {
+ Objects.requireNonNull(mixingRuleUpdates);
+
+ IAudioService service = getService();
+ try {
+ synchronized (mLock) {
+ final int status = service.updateMixingRulesForPolicy(
+ mixingRuleUpdates.stream().map(p -> p.first).toArray(AudioMix[]::new),
+ mixingRuleUpdates.stream().map(p -> p.second).toArray(
+ AudioMixingRule[]::new),
+ cb());
+ if (status == AudioManager.SUCCESS) {
+ mConfig.updateMixingRules(mixingRuleUpdates);
+ }
+ return status;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exeception in updateMixingRules call: ", e);
+ return AudioManager.ERROR;
+ }
+ }
+
+ /**
* @hide
* Configures the audio framework so that all audio streams originating from the given UID
* can only come from a set of audio devices.
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 7a85d21..d277c7d 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -17,16 +17,17 @@
package android.media.audiopolicy;
import android.annotation.NonNull;
-import android.media.AudioFormat;
import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -85,72 +86,20 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mMixes.size());
for (AudioMix mix : mMixes) {
- // write mix route flags
- dest.writeInt(mix.getRouteFlags());
- // write callback flags
- dest.writeInt(mix.mCallbackFlags);
- // write device information
- dest.writeInt(mix.mDeviceSystemType);
- dest.writeString(mix.mDeviceAddress);
- // write mix format
- dest.writeInt(mix.getFormat().getSampleRate());
- dest.writeInt(mix.getFormat().getEncoding());
- dest.writeInt(mix.getFormat().getChannelMask());
- // write opt-out respect
- dest.writeBoolean(mix.getRule().allowPrivilegedMediaPlaybackCapture());
- // write voice communication capture allowed flag
- dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed());
- // write specified mix type
- dest.writeInt(mix.getRule().getTargetMixRole());
- // write mix rules
- final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
- dest.writeInt(criteria.size());
- for (AudioMixMatchCriterion criterion : criteria) {
- criterion.writeToParcel(dest);
- }
+ mix.writeToParcel(dest, flags);
}
}
private AudioPolicyConfig(Parcel in) {
- mMixes = new ArrayList<AudioMix>();
int nbMixes = in.readInt();
+ mMixes = new ArrayList<>(nbMixes);
for (int i = 0 ; i < nbMixes ; i++) {
- final AudioMix.Builder mixBuilder = new AudioMix.Builder();
- // read mix route flags
- int routeFlags = in.readInt();
- mixBuilder.setRouteFlags(routeFlags);
- // read callback flags
- mixBuilder.setCallbackFlags(in.readInt());
- // read device information
- mixBuilder.setDevice(in.readInt(), in.readString());
- // read mix format
- int sampleRate = in.readInt();
- int encoding = in.readInt();
- int channelMask = in.readInt();
- final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
- .setChannelMask(channelMask).setEncoding(encoding).build();
- mixBuilder.setFormat(format);
-
- AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
- // read opt-out respect
- ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean());
- // read voice capture allowed flag
- ruleBuilder.voiceCommunicationCaptureAllowed(in.readBoolean());
- // read specified mix type
- ruleBuilder.setTargetMixRole(in.readInt());
- // read mix rules
- int nbRules = in.readInt();
- for (int j = 0 ; j < nbRules ; j++) {
- // read the matching rules
- ruleBuilder.addRuleFromParcel(in);
- }
- mixBuilder.setMixingRule(ruleBuilder.build());
- mMixes.add(mixBuilder.build());
+ mMixes.add(AudioMix.CREATOR.createFromParcel(in));
}
}
- public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR
- = new Parcelable.Creator<AudioPolicyConfig>() {
+ public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR =
+ new Parcelable.Creator<>() {
/**
* Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
* @param p Parcel object to read the AudioPolicyConfig from
@@ -309,6 +258,23 @@
}
}
+ /**
+ * Update audio mixing rules for already registered {@link AudioMix}-es.
+ *
+ * @param audioMixingRuleUpdates {@link List} of {@link Pair}-s containing {@link AudioMix} to
+ * be updated and the new {@link AudioMixingRule}.
+ */
+ public void updateMixingRules(
+ @NonNull List<Pair<AudioMix, AudioMixingRule>> audioMixingRuleUpdates) {
+ Objects.requireNonNull(audioMixingRuleUpdates).forEach(
+ update -> updateMixingRule(update.first, update.second));
+ }
+
+ private void updateMixingRule(AudioMix audioMixToUpdate, AudioMixingRule audioMixingRule) {
+ mMixes.stream().filter(audioMixToUpdate::equals).findAny().ifPresent(
+ mix -> mix.setAudioMixingRule(audioMixingRule));
+ }
+
private static String mixTypeId(int type) {
if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
deleted file mode 100644
index a26398a..0000000
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2023 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.audiopolicytest;
-
-import static android.media.AudioFormat.CHANNEL_OUT_MONO;
-import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
-import static android.media.AudioFormat.ENCODING_PCM_16BIT;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-
-import android.media.AudioFormat;
-import android.media.AudioSystem;
-import android.media.audiopolicy.AudioMix;
-import android.media.audiopolicy.AudioMixingRule;
-import android.media.audiopolicy.AudioPolicyConfig;
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.testing.EqualsTester;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Unit tests for AudioMix.
- *
- * Run with "atest AudioMixUnitTests".
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AudioMixUnitTests {
- private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM =
- new AudioFormat.Builder()
- .setSampleRate(44000)
- .setChannelMask(CHANNEL_OUT_STEREO)
- .setEncoding(ENCODING_PCM_16BIT).build();
- private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM =
- new AudioFormat.Builder()
- .setSampleRate(16000)
- .setChannelMask(CHANNEL_OUT_MONO)
- .setEncoding(ENCODING_PCM_16BIT).build();
- private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM =
- new AudioFormat.Builder()
- .setSampleRate(16000)
- .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
- .setEncoding(ENCODING_PCM_16BIT).build();
-
- @Test
- public void testEquals() {
- final EqualsTester equalsTester = new EqualsTester();
-
- // --- Equality group 1
- final AudioMix playbackAudioMixWithSessionId42AndUid123 =
- new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
- .addMixRule(RULE_MATCH_UID, 123).build())
- .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
- final AudioMix playbackAudioMixWithUid123AndSessionId42 =
- new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 123)
- .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build())
- .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
- equalsTester.addEqualityGroup(
- playbackAudioMixWithSessionId42AndUid123,
- playbackAudioMixWithUid123AndSessionId42,
- writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123),
- writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42));
-
- // --- Equality group 2
- final AudioMix recordingAudioMixWithSessionId42AndUid123 =
- new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_INJECTOR)
- .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
- .addMixRule(RULE_MATCH_UID, 123).build())
- .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
- final AudioMix recordingAudioMixWithUid123AndSessionId42 =
- new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_INJECTOR)
- .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
- .addMixRule(RULE_MATCH_UID, 123).build())
- .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
- equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123,
- recordingAudioMixWithUid123AndSessionId42,
- writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123),
- writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42));
-
- // --- Equality group 3
- final AudioMix recordingAudioMixWithSessionId42AndUid123Render =
- new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
- .addMixRule(RULE_MATCH_UID, 123).build())
- .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(
- AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build();
- equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render,
- writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render));
-
- // --- Equality group 4
- final AudioMix playbackAudioMixWithUid123 =
- new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 123).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
- equalsTester.addEqualityGroup(playbackAudioMixWithUid123,
- writeToAndFromParcel(playbackAudioMixWithUid123));
-
- // --- Equality group 5
- final AudioMix playbackAudioMixWithUid42 =
- new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
- equalsTester.addEqualityGroup(playbackAudioMixWithUid42,
- writeToAndFromParcel(playbackAudioMixWithUid42));
-
- equalsTester.testEquals();
- }
-
- @Test
- public void buildRenderToRemoteSubmix_success() {
- final String deviceAddress = "address";
- final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
- .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build();
-
- assertEquals(deviceAddress, audioMix.getRegistration());
- assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
- assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags());
- }
-
- @Test
- public void buildLoopbackAndRenderToRemoteSubmix_success() {
- final String deviceAddress = "address";
- final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER)
- .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build();
-
- assertEquals(deviceAddress, audioMix.getRegistration());
- assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
- assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER, audioMix.getRouteFlags());
- }
-
- @Test
- public void buildRenderToSpeaker_success() {
- final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
- .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build();
-
- assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
- assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags());
- }
-
- @Test
- public void buildLoopbackForPlayerMix_success() {
- final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-
- assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
- assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags());
- }
-
- @Test
- public void buildLoopbackForInjectorMix_success() {
- final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_INJECTOR)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
-
- assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
- assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags());
- }
-
- @Test
- public void buildLoopbackWithIncompatibleDevice_throws() {
- assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
- new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
- .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build());
- }
-
- @Test
- public void buildRenderWithoutDevice_throws() {
- assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
- new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build());
- }
-
- @Test
- public void buildRenderWithInputDevice_throws() {
- assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
- new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
- .setDevice(AudioSystem.DEVICE_IN_BUILTIN_MIC, /*address=*/"").build());
- }
-
- @Test
- public void buildRenderWithInjectorMix_throws() {
- assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
- new AudioMixingRule.Builder()
- .setTargetMixRole(MIX_ROLE_INJECTOR)
- .addMixRule(RULE_MATCH_UID, 42).build())
- .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
- .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
- .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build());
- }
-
-
-
- private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
- AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix)));
- Parcel parcel = Parcel.obtain();
- apc.writeToParcel(parcel, /*flags=*/0);
- parcel.setDataPosition(0);
- AudioMix unmarshalledMix =
- AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0);
- parcel.recycle();
- return unmarshalledMix;
- }
-}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
deleted file mode 100644
index 3cbfd50..0000000
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.audiopolicytest;
-
-import static android.media.AudioAttributes.USAGE_MEDIA;
-import static android.media.MediaRecorder.AudioSource.VOICE_RECOGNITION;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
-import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_UID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
-import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-
-
-import android.media.AudioAttributes;
-import android.media.audiopolicy.AudioMixingRule;
-import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.hamcrest.CustomTypeSafeMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for AudioPolicy.
- *
- * Run with "atest AudioMixingRuleUnitTests".
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AudioMixingRuleUnitTests {
- private static final AudioAttributes USAGE_MEDIA_AUDIO_ATTRIBUTES =
- new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
- private static final AudioAttributes CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES =
- new AudioAttributes.Builder().setCapturePreset(VOICE_RECOGNITION).build();
- private static final int TEST_UID = 42;
- private static final int OTHER_UID = 77;
- private static final int TEST_SESSION_ID = 1234;
-
- @Test
- public void testConstructValidRule() {
- AudioMixingRule rule = new AudioMixingRule.Builder()
- .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
- .addMixRule(RULE_MATCH_UID, TEST_UID)
- .excludeMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
- .build();
-
- // Based on the rules, the mix type should fall back to MIX_ROLE_PLAYERS,
- // since the rules are valid for both MIX_ROLE_PLAYERS & MIX_ROLE_INJECTOR.
- assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
- assertThat(rule.getCriteria(), containsInAnyOrder(
- isAudioMixMatchUsageCriterion(USAGE_MEDIA),
- isAudioMixMatchUidCriterion(TEST_UID),
- isAudioMixExcludeSessionCriterion(TEST_SESSION_ID)));
- }
-
- @Test
- public void testConstructRuleWithConflictingCriteriaFails() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder()
- .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
- .addMixRule(RULE_MATCH_UID, TEST_UID)
- // Conflicts with previous criterion.
- .addMixRule(RULE_EXCLUDE_UID, OTHER_UID)
- .build());
- }
-
- @Test
- public void testRuleBuilderDedupsCriteria() {
- AudioMixingRule rule = new AudioMixingRule.Builder()
- .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
- .addMixRule(RULE_MATCH_UID, TEST_UID)
- // Identical to previous criterion.
- .addMixRule(RULE_MATCH_UID, TEST_UID)
- // Identical to first criterion.
- .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
- .build();
-
- assertThat(rule.getCriteria(), hasSize(2));
- assertThat(rule.getCriteria(), containsInAnyOrder(
- isAudioMixMatchUsageCriterion(USAGE_MEDIA),
- isAudioMixMatchUidCriterion(TEST_UID)));
- }
-
- @Test
- public void failsWhenAddAttributeRuleCalledWithInvalidType() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder()
- // Rule match attribute usage requires AudioAttributes, not
- // just the int enum value of the usage.
- .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
- .build());
- }
-
- @Test
- public void failsWhenExcludeAttributeRuleCalledWithInvalidType() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder()
- // Rule match attribute usage requires AudioAttributes, not
- // just the int enum value of the usage.
- .excludeMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA)
- .build());
- }
-
- @Test
- public void failsWhenAddIntRuleCalledWithInvalidType() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder()
- // Rule match uid requires Integer not AudioAttributes.
- .addMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
- .build());
- }
-
- @Test
- public void failsWhenExcludeIntRuleCalledWithInvalidType() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder()
- // Rule match uid requires Integer not AudioAttributes.
- .excludeMixRule(RULE_MATCH_UID, USAGE_MEDIA_AUDIO_ATTRIBUTES)
- .build());
- }
-
- @Test
- public void injectorMixTypeDeductionWithGenericRuleSucceeds() {
- AudioMixingRule rule = new AudioMixingRule.Builder()
- // UID rule can be used both with MIX_ROLE_PLAYERS and MIX_ROLE_INJECTOR.
- .addMixRule(RULE_MATCH_UID, TEST_UID)
- // Capture preset rule is only valid for injector, MIX_ROLE_INJECTOR should
- // be deduced.
- .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
- CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
- .build();
-
- assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
- assertThat(rule.getCriteria(), containsInAnyOrder(
- isAudioMixMatchUidCriterion(TEST_UID),
- isAudioMixMatchCapturePresetCriterion(VOICE_RECOGNITION)));
- }
-
- @Test
- public void settingTheMixTypeToIncompatibleInjectorMixFails() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder()
- .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
- CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
- // Capture preset cannot be defined for MIX_ROLE_PLAYERS.
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .build());
- }
-
- @Test
- public void addingPlayersOnlyRuleWithInjectorsOnlyRuleFails() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder()
- // MIX_ROLE_PLAYERS only rule.
- .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
- // MIX ROLE_INJECTOR only rule.
- .addMixRule(RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
- CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES)
- .build());
- }
-
- @Test
- public void sessionIdRuleCompatibleWithPlayersMix() {
- int sessionId = 42;
- AudioMixingRule rule = new AudioMixingRule.Builder()
- .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, sessionId)
- .setTargetMixRole(MIX_ROLE_PLAYERS)
- .build();
-
- assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
- assertThat(rule.getCriteria(), containsInAnyOrder(isAudioMixSessionCriterion(sessionId)));
- }
-
- @Test
- public void sessionIdRuleCompatibleWithInjectorMix() {
- AudioMixingRule rule = new AudioMixingRule.Builder()
- .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
- .setTargetMixRole(MIX_ROLE_INJECTOR)
- .build();
-
- assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
- assertThat(rule.getCriteria(),
- containsInAnyOrder(isAudioMixSessionCriterion(TEST_SESSION_ID)));
- }
-
- @Test
- public void audioMixingRuleWithNoRulesFails() {
- assertThrows(IllegalArgumentException.class,
- () -> new AudioMixingRule.Builder().build());
- }
-
-
- private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) {
- return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
- @Override
- public boolean matchesSafely(AudioMixMatchCriterion item) {
- int expectedRule = exclude ? RULE_EXCLUDE_UID : RULE_MATCH_UID;
- return item.getRule() == expectedRule && item.getIntProp() == uid;
- }
-
- @Override
- public void describeMismatchSafely(
- AudioMixMatchCriterion item, Description mismatchDescription) {
- mismatchDescription.appendText(
- String.format("is not %s criterion with uid %d",
- exclude ? "exclude" : "match", uid));
- }
- };
- }
-
- private static Matcher isAudioMixMatchUidCriterion(int uid) {
- return isAudioMixUidCriterion(uid, /*exclude=*/ false);
- }
-
- private static Matcher isAudioMixCapturePresetCriterion(int audioSource, boolean exclude) {
- return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
- @Override
- public boolean matchesSafely(AudioMixMatchCriterion item) {
- int expectedRule = exclude
- ? RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET
- : RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
- AudioAttributes attributes = item.getAudioAttributes();
- return item.getRule() == expectedRule
- && attributes != null && attributes.getCapturePreset() == audioSource;
- }
-
- @Override
- public void describeMismatchSafely(
- AudioMixMatchCriterion item, Description mismatchDescription) {
- mismatchDescription.appendText(
- String.format("is not %s criterion with capture preset %d",
- exclude ? "exclude" : "match", audioSource));
- }
- };
- }
-
- private static Matcher isAudioMixMatchCapturePresetCriterion(int audioSource) {
- return isAudioMixCapturePresetCriterion(audioSource, /*exclude=*/ false);
- }
-
- private static Matcher isAudioMixUsageCriterion(int usage, boolean exclude) {
- return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("usage mix criterion") {
- @Override
- public boolean matchesSafely(AudioMixMatchCriterion item) {
- int expectedRule =
- exclude ? RULE_EXCLUDE_ATTRIBUTE_USAGE : RULE_MATCH_ATTRIBUTE_USAGE;
- AudioAttributes attributes = item.getAudioAttributes();
- return item.getRule() == expectedRule
- && attributes != null && attributes.getUsage() == usage;
- }
-
- @Override
- public void describeMismatchSafely(
- AudioMixMatchCriterion item, Description mismatchDescription) {
- mismatchDescription.appendText(
- String.format("is not %s criterion with usage %d",
- exclude ? "exclude" : "match", usage));
- }
- };
- }
-
- private static Matcher isAudioMixMatchUsageCriterion(int usage) {
- return isAudioMixUsageCriterion(usage, /*exclude=*/ false);
- }
-
- private static Matcher isAudioMixSessionCriterion(int sessionId, boolean exclude) {
- return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("sessionId mix criterion") {
- @Override
- public boolean matchesSafely(AudioMixMatchCriterion item) {
- int excludeRule =
- exclude ? RULE_EXCLUDE_AUDIO_SESSION_ID : RULE_MATCH_AUDIO_SESSION_ID;
- return item.getRule() == excludeRule && item.getIntProp() == sessionId;
- }
-
- @Override
- public void describeMismatchSafely(
- AudioMixMatchCriterion item, Description mismatchDescription) {
- mismatchDescription.appendText(
- String.format("is not %s criterion with session id %d",
- exclude ? "exclude" : "match", sessionId));
- }
- };
- }
-
- private static Matcher isAudioMixSessionCriterion(int sessionId) {
- return isAudioMixSessionCriterion(sessionId, /*exclude=*/ false);
- }
-
- private static Matcher isAudioMixExcludeSessionCriterion(int sessionId) {
- return isAudioMixSessionCriterion(sessionId, /*exclude=*/ true);
- }
-
-}
diff --git a/packages/EncryptedLocalTransport/Android.bp b/packages/EncryptedLocalTransport/Android.bp
index 09e5630..9ae6d96 100644
--- a/packages/EncryptedLocalTransport/Android.bp
+++ b/packages/EncryptedLocalTransport/Android.bp
@@ -27,9 +27,6 @@
name: "EncryptedLocalTransport",
defaults: ["platform_app_defaults"],
srcs: ["src/**/*.java"],
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
static_libs: ["LocalTransport"],
platform_apis: true,
certificate: "platform",
diff --git a/packages/EncryptedLocalTransport/proguard.flags b/packages/EncryptedLocalTransport/proguard.flags
deleted file mode 100644
index e4ce3c5..0000000
--- a/packages/EncryptedLocalTransport/proguard.flags
+++ /dev/null
@@ -1,2 +0,0 @@
--keep class com.android.localTransport.EncryptedLocalTransport
--keep class com.android.localTransport.EncryptedLocalTransportService
diff --git a/packages/LocalTransport/Android.bp b/packages/LocalTransport/Android.bp
index d4fa191..e7a273b 100644
--- a/packages/LocalTransport/Android.bp
+++ b/packages/LocalTransport/Android.bp
@@ -27,9 +27,7 @@
name: "LocalTransport",
defaults: ["platform_app_defaults"],
srcs: ["src/**/*.java"],
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
+ libs: ["keepanno-annotations"],
platform_apis: true,
certificate: "platform",
privileged: true,
diff --git a/packages/LocalTransport/proguard.flags b/packages/LocalTransport/proguard.flags
deleted file mode 100644
index c1f51b8..0000000
--- a/packages/LocalTransport/proguard.flags
+++ /dev/null
@@ -1,5 +0,0 @@
--keep class com.android.localTransport.LocalTransport
--keep class com.android.localTransport.LocalTransportParameters
--keep class com.android.localTransport.LocalTransportService
-
-
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 73f8730..933be11 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -38,6 +38,9 @@
import android.util.Base64;
import android.util.Log;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+
import libcore.io.IoUtils;
import java.io.BufferedOutputStream;
@@ -127,6 +130,13 @@
return mParameters;
}
+
+ @UsesReflection({
+ // As the runtime class name is used to generate the returned name, and the returned
+ // name may be used used with reflection, generate the necessary keep rules.
+ @KeepTarget(classConstant = LocalTransport.class),
+ @KeepTarget(extendsClassConstant = LocalTransport.class)
+ })
@Override
public String name() {
return new ComponentName(mContext, this.getClass()).flattenToShortString();
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 3d35bad..5dcb9d2 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -8,7 +8,6 @@
}
android_library {
-
name: "SettingsLib",
static_libs: [
@@ -26,44 +25,20 @@
"iconloader",
"WifiTrackerLibRes",
- "SettingsLibHelpUtils",
- "SettingsLibRestrictedLockUtils",
- "SettingsLibActionBarShadow",
- "SettingsLibAppPreference",
- "SettingsLibSearchWidget",
- "SettingsLibSettingsSpinner",
- "SettingsLibIllustrationPreference",
- "SettingsLibLayoutPreference",
- "SettingsLibMainSwitchPreference",
- "SettingsLibActionButtonsPreference",
- "SettingsLibEntityHeaderWidgets",
- "SettingsLibBarChartPreference",
- "SettingsLibProgressBar",
- "SettingsLibAdaptiveIcon",
- "SettingsLibRadioButtonPreference",
- "SettingsLibSelectorWithWidgetPreference",
- "SettingsLibDisplayUtils",
- "SettingsLibUtils",
- "SettingsLibEmergencyNumber",
- "SettingsLibTopIntroPreference",
- "SettingsLibBannerMessagePreference",
- "SettingsLibFooterPreference",
- "SettingsLibUsageProgressBarPreference",
- "SettingsLibCollapsingToolbarBaseActivity",
- "SettingsLibTwoTargetPreference",
- "SettingsLibSettingsTransition",
- "SettingsLibButtonPreference",
"SettingsLibDeviceStateRotationLock",
- "SettingsLibProfileSelector",
+ "SettingsLibDisplayUtils",
+ "SettingsLibEmergencyNumber",
+ "SettingsLibSearchWidget",
+ "SettingsLibUtils",
+ "SettingsLibWidget",
"setupdesign",
"zxing-core-1.7",
"androidx.room_room-runtime",
"settingslib_flags_lib",
-
],
plugins: ["androidx.room_room-compiler-plugin"],
-
+ use_resource_processor: true,
resource_dirs: ["res"],
srcs: [
@@ -72,6 +47,43 @@
],
}
+// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to
+// set use_resource_processor = true.
+// We can remove SettingsLibWidget when all these libraries have its own namespace.
+android_library {
+ name: "SettingsLibWidget",
+ visibility: ["//visibility:private"],
+ manifest: "AndroidManifest-SettingsLibWidget.xml",
+ static_libs: [
+ "SettingsLibActionBarShadow",
+ "SettingsLibActionButtonsPreference",
+ "SettingsLibAdaptiveIcon",
+ "SettingsLibAppPreference",
+ "SettingsLibBannerMessagePreference",
+ "SettingsLibBarChartPreference",
+ "SettingsLibButtonPreference",
+ "SettingsLibCollapsingToolbarBaseActivity",
+ "SettingsLibEntityHeaderWidgets",
+ "SettingsLibFooterPreference",
+ "SettingsLibHelpUtils",
+ "SettingsLibIllustrationPreference",
+ "SettingsLibLayoutPreference",
+ "SettingsLibMainSwitchPreference",
+ "SettingsLibProfileSelector",
+ "SettingsLibProgressBar",
+ "SettingsLibRadioButtonPreference",
+ "SettingsLibRestrictedLockUtils",
+ "SettingsLibSelectorWithWidgetPreference",
+ "SettingsLibSettingsSpinner",
+ "SettingsLibSettingsTransition",
+ "SettingsLibTopIntroPreference",
+ "SettingsLibTwoTargetPreference",
+ "SettingsLibUsageProgressBarPreference",
+ ],
+
+ resource_dirs: [],
+}
+
// NOTE: Keep this module in sync with ./common.mk
java_defaults {
name: "SettingsLibDefaults",
diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
new file mode 100644
index 0000000..38a7d6a
--- /dev/null
+++ b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+-->
+
+<manifest package="com.android.settingslib.widget" />
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index af06d73..b1d1ea5e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -126,10 +126,10 @@
switchSummary = isChecked()
? getUpdatableEnterpriseString(
getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
- R.string.enabled_by_admin)
+ com.android.settingslib.widget.R.string.enabled_by_admin)
: getUpdatableEnterpriseString(
getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
- R.string.disabled_by_admin);
+ com.android.settingslib.widget.R.string.disabled_by_admin);
} else {
switchSummary = mRestrictedSwitchSummary;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 6eb2f38..96bb4b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1776,7 +1776,8 @@
final int userId = UserHandle.getUserId(this.info.uid);
if (UserManager.get(context).isManagedProfile(userId)) {
this.labelDescription = context.getString(
- com.android.settingslib.R.string.accessibility_work_profile_app_description,
+ com.android.settingslib.utils.R
+ .string.accessibility_work_profile_app_description,
this.label);
} else {
this.labelDescription = this.label;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
index cb4eba4..f5257b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
@@ -46,7 +46,8 @@
View layout = View.inflate(mContext, R.layout.broadcast_dialog, null);
final Window window = getWindow();
window.setContentView(layout);
- window.setWindowAnimations(R.style.Theme_AlertDialog_SettingsLib);
+ window.setWindowAnimations(
+ com.android.settingslib.widget.R.style.Theme_AlertDialog_SettingsLib);
TextView title = layout.findViewById(R.id.dialog_title);
TextView subTitle = layout.findViewById(R.id.dialog_subtitle);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 1f4cafce..d53c3a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -153,7 +153,7 @@
}
final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
final int iconSize = getContext().getResources().getDimensionPixelSize(
- R.dimen.secondary_app_icon_size);
+ com.android.settingslib.widget.R.dimen.secondary_app_icon_size);
if (icon != null && iconSize > 0) {
ViewGroup.LayoutParams params = icon.getLayoutParams();
params.height = iconSize;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
index ebdfbea..251cd36 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -32,7 +32,6 @@
import android.util.Log;
import com.android.launcher3.icons.BaseIconFactory;
-import com.android.settingslib.R;
import com.android.settingslib.Utils;
/**
@@ -81,7 +80,7 @@
mPackageManager = pm;
mIconDrawableFactory = iconDrawableFactory;
mImportantConversationColor = context.getResources().getColor(
- R.color.important_conversation, null);
+ com.android.launcher3.icons.R.color.important_conversation, null);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 21eb35a..2d6f058 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -167,7 +167,8 @@
ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
bindFrictionImage(frictionImageView);
- final View divider = view.findViewById(R.id.two_target_divider);
+ final View divider =
+ view.findViewById(com.android.settingslib.widget.R.id.two_target_divider);
divider.setVisibility(shouldShowDivider() ? View.VISIBLE : View.INVISIBLE);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index c45d774..015356e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -331,7 +331,8 @@
return;
} else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
&& mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
- statusLabel = mContext.getString(R.string.wifi_connected_low_quality);
+ statusLabel = mContext.getString(
+ com.android.wifitrackerlib.R.string.wifi_connected_low_quality);
return;
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
index 74c2fc8..32a16716 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
@@ -54,7 +54,7 @@
mPreference = new PrimarySwitchPreference(mContext);
LayoutInflater inflater = LayoutInflater.from(mContext);
mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate(
- com.android.settingslib.R.layout.preference_two_target, null));
+ com.android.settingslib.widget.R.layout.preference_two_target, null));
mWidgetView = mHolder.itemView.findViewById(android.R.id.widget_frame);
inflater.inflate(R.layout.preference_widget_primary_switch, mWidgetView, true);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
index 06343f5..ff84dc9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
@@ -55,7 +55,8 @@
@Test
public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
- View contentFrameView = layout.findViewById(R.id.content_frame);
+ View contentFrameView =
+ layout.findViewById(com.android.settingslib.widget.R.id.content_frame);
TextView textView = contentFrameView.findViewById(R.id.text_hello_world);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
index 471dac0..8246aff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -29,8 +29,6 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
-import com.android.settingslib.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -95,7 +93,7 @@
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setTheme(R.style.Theme_AppCompat);
+ setTheme(androidx.appcompat.R.style.Theme_AppCompat);
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
index 7b08fee..2f8967e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
@@ -30,8 +30,6 @@
import androidx.test.core.app.ApplicationProvider;
-import com.android.settingslib.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,7 +49,7 @@
@Before
public void setUp() {
- mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+ mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
mController.initialize(mTestUtils.createLearnMoreButtonLauncher());
mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
index 7b88566..f168122 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
@@ -33,8 +33,6 @@
import androidx.test.core.app.ApplicationProvider;
-import com.android.settingslib.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,7 +55,7 @@
@Before
public void setUp() {
- mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+ mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
index 66a2ea6..6831222 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -62,7 +62,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
- mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+ mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
mUnderTest = new TestCreateUserDialogController();
mPhotoRestrictedByBase = false;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index f595cd3..a95257a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -99,7 +99,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
- mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+ mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
mController = new TestEditUserInfoController();
mPhotoRestrictedByBase = false;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
index 6cbae05..10862403 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
@@ -105,15 +105,15 @@
icon.setBackgroundColor(mContext, tile);
- assertThat(icon.mBackgroundColor)
- .isEqualTo(mContext.getColor(R.color.homepage_generic_icon_background));
+ assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
+ com.android.settingslib.widget.R.color.homepage_generic_icon_background));
}
@Test
public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() {
final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE));
mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT,
- R.color.bt_outline_color);
+ com.android.settingslib.widget.R.color.bt_outline_color);
doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update))
.when(tile).getIcon(mContext);
@@ -121,8 +121,8 @@
new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK));
icon.setBackgroundColor(mContext, tile);
- assertThat(icon.mBackgroundColor)
- .isEqualTo(mContext.getColor(R.color.bt_outline_color));
+ assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
+ com.android.settingslib.widget.R.color.bt_outline_color));
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
index 71d55bc..b2bc53d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
@@ -21,8 +21,6 @@
import android.content.res.Resources;
import android.graphics.Paint;
-import com.android.settingslib.R;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 049c90e..a26f200 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -58,14 +58,15 @@
@Test
public void setLearnMoreText_shouldSetAsTextInLearnMore() {
final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
- LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
+ LayoutInflater.from(mContext)
+ .inflate(com.android.settingslib.widget.R.layout.preference_footer, null));
mFooterPreference.setLearnMoreText("Custom learn more");
mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
mFooterPreference.onBindViewHolder(holder);
assertThat(((TextView) holder.findViewById(
- R.id.settingslib_learn_more)).getText().toString())
+ com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString())
.isEqualTo("Custom learn more");
}
@@ -94,8 +95,9 @@
@Test
public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
- LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
- when(viewHolder.findViewById(R.id.title)).thenReturn(null);
+ LayoutInflater.from(mContext)
+ .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+ when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null);
Throwable actualThrowable = null;
try {
@@ -110,8 +112,10 @@
@Test
public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
- LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
- when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
+ LayoutInflater.from(mContext)
+ .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+ when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more))
+ .thenReturn(null);
Throwable actualThrowable = null;
try {
@@ -126,7 +130,8 @@
@Test
public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
- LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+ LayoutInflater.from(mContext)
+ .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
Throwable actualThrowable = null;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
index aaec909..23b4c2a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
@@ -32,8 +32,6 @@
import androidx.preference.PreferenceViewHolder;
-import com.android.settingslib.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 29bc409..089ddbe 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -331,6 +331,23 @@
"tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt",
"tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt",
"tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt",
+
+ /* Dream tests */
+ "tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java",
+ "tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java",
+ "tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java",
+ "tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java",
+ "tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java",
+ "tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java",
+ "tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java",
+ "tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt",
+ "tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt",
+ "tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java",
+ "tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java",
+ "tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java",
+ "tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java",
+ "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java",
+ "tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java",
],
path: "tests/src",
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 0d7d9cc..017ac60 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -446,7 +446,9 @@
for (key in listOf(".blue600", ".blue400")) {
addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
PorterDuffColorFilter(
- context.getColor(com.android.settingslib.R.color.settingslib_color_blue400),
+ context.getColor(
+ com.android.settingslib.color.R.color.settingslib_color_blue400
+ ),
PorterDuff.Mode.SRC_ATOP
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index e7bbf97..f68078a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -22,6 +22,7 @@
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
import android.os.Handler
+import android.os.Trace
import android.util.Log
import android.view.Display
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -34,9 +35,14 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
@@ -49,7 +55,25 @@
*
* When `null`, it means there is no pending display waiting to be enabled.
*/
- val pendingDisplayId: Flow<Int?>
+ val pendingDisplay: Flow<PendingDisplay?>
+
+ /** Represents a connected display that has not been enabled yet. */
+ interface PendingDisplay {
+ /** Id of the pending display. */
+ val id: Int
+
+ /** Enables the display, making it available to the system. */
+ suspend fun enable()
+
+ /**
+ * Ignores the pending display. When called, this specific display id doesn't appear as
+ * pending anymore until the display is disconnected and reconnected again.
+ */
+ suspend fun ignore()
+
+ /** Disables the display, making it unavailable to the system. */
+ suspend fun disable()
+ }
}
@SysUISingleton
@@ -62,7 +86,8 @@
@Background backgroundCoroutineDispatcher: CoroutineDispatcher
) : DisplayRepository {
- override val displays: Flow<Set<Display>> =
+ // Displays are enabled only after receiving them in [onDisplayAdded]
+ private val enabledDisplays: StateFlow<Set<Display>> =
conflatedCallbackFlow {
val callback =
object : DisplayListener {
@@ -99,27 +124,38 @@
displayManager.displays?.toSet() ?: emptySet()
}
- override val pendingDisplayId: Flow<Int?> =
+ /** Propagate to the listeners only enabled displays */
+ override val displays: Flow<Set<Display>> = enabledDisplays
+
+ private val enabledDisplayIds: Flow<Set<Int>> =
+ enabledDisplays
+ .map { enabledDisplaysSet -> enabledDisplaysSet.map { it.displayId }.toSet() }
+ .debugLog("enabledDisplayIds")
+
+ private val ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
+
+ /* keeps connected displays until they are disconnected. */
+ private val connectedDisplayIds: StateFlow<Set<Int>> =
conflatedCallbackFlow {
val callback =
object : DisplayConnectionListener {
- private val pendingIds = mutableSetOf<Int>()
+ private val connectedIds = mutableSetOf<Int>()
override fun onDisplayConnected(id: Int) {
- pendingIds += id
- trySend(id)
+ if (DEBUG) {
+ Log.d(TAG, "$id connected")
+ }
+ connectedIds += id
+ ignoredDisplayIds.value -= id
+ trySend(connectedIds.toSet())
}
override fun onDisplayDisconnected(id: Int) {
- if (id in pendingIds) {
- pendingIds -= id
- trySend(null)
- } else {
- Log.e(
- TAG,
- "onDisplayDisconnected received for unknown display. " +
- "id=$id, knownIds=$pendingIds"
- )
+ connectedIds -= id
+ if (DEBUG) {
+ Log.d(TAG, "$id disconnected. Connected ids: $connectedIds")
}
+ ignoredDisplayIds.value -= id
+ trySend(connectedIds.toSet())
}
}
displayManager.registerDisplayListener(
@@ -130,15 +166,80 @@
awaitClose { displayManager.unregisterDisplayListener(callback) }
}
.distinctUntilChanged()
+ .debugLog("connectedDisplayIds")
.flowOn(backgroundCoroutineDispatcher)
.stateIn(
applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = null
+ initialValue = emptySet()
)
+ /**
+ * Pending displays are the ones connected, but not enabled and not ignored. A connected display
+ * is ignored after the user makes the decision to use it or not. For now, the initial decision
+ * from the user is final and not reversible.
+ */
+ private val pendingDisplayIds: Flow<Set<Int>> =
+ combine(enabledDisplayIds, connectedDisplayIds, ignoredDisplayIds) {
+ enabledDisplaysIds,
+ connectedDisplayIds,
+ ignoredDisplayIds ->
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "combining enabled: $enabledDisplaysIds, " +
+ "connected: $connectedDisplayIds, ignored: $ignoredDisplayIds"
+ )
+ }
+ connectedDisplayIds - enabledDisplaysIds - ignoredDisplayIds
+ }
+ .debugLog("pendingDisplayIds")
+
+ override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> =
+ pendingDisplayIds
+ .map { pendingDisplayIds ->
+ val id = pendingDisplayIds.maxOrNull() ?: return@map null
+ object : DisplayRepository.PendingDisplay {
+ override val id = id
+ override suspend fun enable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.enableConnectedDisplay(id)
+ }
+ // After the display has been enabled, it is automatically ignored.
+ ignore()
+ }
+
+ override suspend fun ignore() {
+ traceSection("DisplayRepository#ignore($id)") {
+ ignoredDisplayIds.value += id
+ }
+ }
+
+ override suspend fun disable() {
+ ignore()
+ traceSection("DisplayRepository#disable($id)") {
+ displayManager.disableConnectedDisplay(id)
+ }
+ }
+ }
+ }
+ .debugLog("pendingDisplay")
+
+ private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
+ return if (DEBUG) {
+ this.onEach {
+ Log.d(TAG, "$flowName: $it")
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "$TAG#$flowName", 0)
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "$TAG#$flowName", "$it", 0)
+ }
+ } else {
+ this
+ }
+ }
+
private companion object {
const val TAG = "DisplayRepository"
+ val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index ef6fa26..11ed96d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -16,14 +16,12 @@
package com.android.systemui.display.domain.interactor
-import android.hardware.display.DisplayManager
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.util.traceSection
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -52,13 +50,18 @@
CONNECTED_SECURE,
}
- /** Represents a connected display that has not been enabled yet. */
+ /** Represents a connected display that has not been enabled yet for the UI layer. */
interface PendingDisplay {
/** Enables the display, making it available to the system. */
- fun enable()
+ suspend fun enable()
- /** Disables the display, making it unavailable to the system. */
- fun disable()
+ /**
+ * Ignores the pending display.
+ *
+ * When called, this specific display id doesn't appear as pending anymore until the display
+ * is disconnected and reconnected again.
+ */
+ suspend fun ignore()
}
}
@@ -66,7 +69,6 @@
class ConnectedDisplayInteractorImpl
@Inject
constructor(
- private val displayManager: DisplayManager,
keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
) : ConnectedDisplayInteractor {
@@ -92,28 +94,19 @@
// Provides the pending display only if the lockscreen is unlocked
override val pendingDisplay: Flow<PendingDisplay?> =
- displayRepository.pendingDisplayId.combine(keyguardRepository.isKeyguardUnlocked) {
- pendingDisplayId,
- keyguardUnlocked ->
- if (pendingDisplayId != null && keyguardUnlocked) {
- pendingDisplayId.toPendingDisplay()
+ displayRepository.pendingDisplay.combine(keyguardRepository.isKeyguardShowing) {
+ repositoryPendingDisplay,
+ keyguardShowing ->
+ if (repositoryPendingDisplay != null && !keyguardShowing) {
+ repositoryPendingDisplay.toInteractorPendingDisplay()
} else {
null
}
}
- private fun Int.toPendingDisplay() =
+ private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
object : PendingDisplay {
- val id = this@toPendingDisplay
- override fun enable() {
- traceSection("DisplayRepository#enable($id)") {
- displayManager.enableConnectedDisplay(id)
- }
- }
- override fun disable() {
- traceSection("DisplayRepository#enable($id)") {
- displayManager.disableConnectedDisplay(id)
- }
- }
+ override suspend fun enable() = this@toInteractorPendingDisplay.enable()
+ override suspend fun ignore() = this@toInteractorPendingDisplay.ignore()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 174c6ff..ecc9d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -24,15 +24,22 @@
import android.widget.TextView
import com.android.systemui.R
-/** Dialog used to decide what to do with a connected display. */
+/**
+ * Dialog used to decide what to do with a connected display.
+ *
+ * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
+ * pressed.
+ */
class MirroringConfirmationDialog(
context: Context,
private val onStartMirroringClickListener: View.OnClickListener,
- private val onDismissClickListener: View.OnClickListener,
+ private val onCancelMirroring: View.OnClickListener,
) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
+ private var enabledPressed = false
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window?.apply {
@@ -45,10 +52,15 @@
mirrorButton =
requireViewById<TextView>(R.id.enable_display).apply {
setOnClickListener(onStartMirroringClickListener)
+ enabledPressed = true
}
dismissButton =
- requireViewById<TextView>(R.id.cancel).apply {
- setOnClickListener(onDismissClickListener)
+ requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
+
+ setOnDismissListener {
+ if (!enabledPressed) {
+ onCancelMirroring.onClick(null)
}
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index ece33b7..86ef439 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -19,13 +19,16 @@
import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.ui.view.MirroringConfirmationDialog
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/**
* Shows/hides a dialog to allow the user to decide whether to use the external display for
@@ -38,6 +41,7 @@
private val context: Context,
private val connectedDisplayInteractor: ConnectedDisplayInteractor,
@Application private val scope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher
) {
private var dialog: Dialog? = null
@@ -61,10 +65,13 @@
MirroringConfirmationDialog(
context,
onStartMirroringClickListener = {
- pendingDisplay.enable()
+ scope.launch(bgDispatcher) { pendingDisplay.enable() }
hideDialog()
},
- onDismissClickListener = { hideDialog() }
+ onCancelMirroring = {
+ scope.launch(bgDispatcher) { pendingDisplay.ignore() }
+ hideDialog()
+ }
)
.apply { show() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0fb1b48..d8b31a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -43,6 +43,10 @@
val ADD_TRANSIENT_HUN_IN_STACK_STATE_ANIMATOR =
unreleasedFlag("add_transient_hun_in_stack_state_animator", teamfood = false)
+ // TODO(b/298308067): Tracking Bug
+ val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
+ unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = false)
+
// TODO(b/254512751): Tracking Bug
val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
unreleasedFlag("notification_pipeline_developer_logging")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 6bc9abf..257006e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -126,11 +126,11 @@
}
override fun start() {
+ bindKeyguardRootView()
if (featureFlags.isEnabled(Flags.LAZY_INFLATE_KEYGUARD)) {
keyguardRootView.removeAllViews()
initializeViews()
} else {
- bindKeyguardRootView()
val notificationPanel =
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
unbindKeyguardBottomArea(notificationPanel)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index a9d2b30..4be572f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -55,7 +55,6 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
import com.android.systemui.screenrecord.ScreenShareOption;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -73,7 +72,6 @@
private final FeatureFlags mFeatureFlags;
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
- private final ActivityStarter mActivityStarter;
private String mPackageName;
private int mUid;
@@ -89,10 +87,8 @@
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
- Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
- ActivityStarter activityStarter) {
+ Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
mFeatureFlags = featureFlags;
- mActivityStarter = activityStarter;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
}
@@ -313,16 +309,8 @@
// Start activity from the current foreground user to avoid creating a separate
// SystemUI process without access to recent tasks because it won't have
// WM Shell running inside.
- // It is also important to make sure the shade is dismissed, otherwise users won't
- // see the app selector.
mUserSelectingTask = true;
- mActivityStarter.startActivity(
- intent,
- /* dismissShade= */ true,
- /* animationController= */ null,
- /* showOverLockscreenWhenLocked= */ false,
- UserHandle.of(ActivityManager.getCurrentUser())
- );
+ startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
}
} catch (RemoteException e) {
Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 8d3b745..1d820a1 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -70,6 +70,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.PreferenceManager;
@@ -85,9 +86,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.people.NotificationHelper;
import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.PeopleSpaceUtils;
@@ -99,6 +102,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.wm.shell.bubbles.Bubbles;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -119,7 +123,8 @@
/** Manager for People Space widget. */
@SysUISingleton
-public class PeopleSpaceWidgetManager {
+public class PeopleSpaceWidgetManager implements Dumpable {
+
private static final String TAG = "PeopleSpaceWidgetMgr";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
@@ -160,7 +165,8 @@
CommonNotifCollection notifCollection,
PackageManager packageManager, Optional<Bubbles> bubblesOptional,
UserManager userManager, NotificationManager notificationManager,
- BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor) {
+ BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
+ DumpManager dumpManager) {
if (DEBUG) Log.d(TAG, "constructor");
mContext = context;
mAppWidgetManager = AppWidgetManager.getInstance(context);
@@ -180,6 +186,7 @@
mManager = this;
mBroadcastDispatcher = broadcastDispatcher;
mBgExecutor = bgExecutor;
+ dumpManager.registerNormalDumpable(TAG, this);
}
/** Initializes {@PeopleSpaceWidgetManager}. */
@@ -1364,4 +1371,40 @@
.filter(id -> !TextUtils.isEmpty(id))
.collect(Collectors.toSet());
}
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".dump");
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ Map<String, ?> all = sp.getAll();
+ pw.println("People widget list:");
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
+ String key = entry.getKey();
+ PeopleBackupHelper.SharedFileEntryType keyType = getEntryType(entry);
+ switch (keyType) {
+ case WIDGET_ID:
+ SharedPreferences widgetSp = mContext.getSharedPreferences(key,
+ Context.MODE_PRIVATE);
+ pw.print("People widget (valid) [");
+ pw.print(key);
+ pw.print("] shortcut id: \"");
+ pw.print(widgetSp.getString(SHORTCUT_ID, EMPTY_STRING));
+ pw.print("\", user id: ");
+ pw.print(widgetSp.getInt(USER_ID, INVALID_USER_ID));
+ pw.print(", package: ");
+ pw.println(widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
+ break;
+ case PEOPLE_TILE_KEY:
+ case CONTACT_URI:
+ pw.print("Extra data [");
+ pw.print(key);
+ pw.print(" : ");
+ pw.print((Set<String>) entry.getValue());
+ pw.println("]");
+ break;
+ }
+ }
+
+ Trace.traceEnd(Trace.TRACE_TAG_APP);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index db7c003..4bd380e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -52,6 +52,7 @@
private val displayManager = mock<DisplayManager>()
private val displayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
+ private val connectedDisplayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
private val testHandler = FakeHandler(Looper.getMainLooper())
private val testScope = TestScope(UnconfinedTestDispatcher())
@@ -114,7 +115,7 @@
// Let's make sure it has *NOT* been unregistered, as there is still a subscriber.
setDisplays(1)
- displayListener.value.onDisplayAdded(1)
+ sendOnDisplayAdded(1)
assertThat(firstSubscriber?.ids()).containsExactly(1)
}
@@ -127,7 +128,7 @@
val value by latestDisplayFlowValue()
setDisplays(1)
- displayListener.value.onDisplayAdded(1)
+ sendOnDisplayAdded(1)
assertThat(value?.ids()).containsExactly(1)
}
@@ -138,13 +139,13 @@
val value by latestDisplayFlowValue()
setDisplays(1, 2, 3, 4)
- displayListener.value.onDisplayAdded(1)
- displayListener.value.onDisplayAdded(2)
- displayListener.value.onDisplayAdded(3)
- displayListener.value.onDisplayAdded(4)
+ sendOnDisplayAdded(1)
+ sendOnDisplayAdded(2)
+ sendOnDisplayAdded(3)
+ sendOnDisplayAdded(4)
setDisplays(1, 2, 3)
- displayListener.value.onDisplayRemoved(4)
+ sendOnDisplayRemoved(4)
assertThat(value?.ids()).containsExactly(1, 2, 3)
}
@@ -155,10 +156,10 @@
val value by latestDisplayFlowValue()
setDisplays(1, 2, 3, 4)
- displayListener.value.onDisplayAdded(1)
- displayListener.value.onDisplayAdded(2)
- displayListener.value.onDisplayAdded(3)
- displayListener.value.onDisplayAdded(4)
+ sendOnDisplayAdded(1)
+ sendOnDisplayAdded(2)
+ sendOnDisplayAdded(3)
+ sendOnDisplayAdded(4)
displayListener.value.onDisplayChanged(4)
@@ -168,22 +169,22 @@
@Test
fun onDisplayConnected_pendingDisplayReceived() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
+ val pendingDisplay by lastPendingDisplay()
- displayListener.value.onDisplayConnected(1)
+ sendOnDisplayConnected(1)
- assertThat(pendingDisplay).isEqualTo(1)
+ assertThat(pendingDisplay!!.id).isEqualTo(1)
}
@Test
fun onDisplayDisconnected_pendingDisplayNull() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
- displayListener.value.onDisplayConnected(1)
+ val pendingDisplay by lastPendingDisplay()
+ sendOnDisplayConnected(1)
assertThat(pendingDisplay).isNotNull()
- displayListener.value.onDisplayDisconnected(1)
+ sendOnDisplayDisconnected(1)
assertThat(pendingDisplay).isNull()
}
@@ -191,24 +192,162 @@
@Test
fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
- displayListener.value.onDisplayConnected(1)
+ val pendingDisplay by lastPendingDisplay()
+ sendOnDisplayConnected(1)
assertThat(pendingDisplay).isNotNull()
- displayListener.value.onDisplayDisconnected(2)
+ sendOnDisplayDisconnected(2)
assertThat(pendingDisplay).isNotNull()
}
@Test
- fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() =
+ fun onDisplayConnected_multipleTimes_sendsOnlyTheMaximum() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
- displayListener.value.onDisplayConnected(1)
- displayListener.value.onDisplayConnected(2)
+ val pendingDisplay by lastPendingDisplay()
- assertThat(pendingDisplay).isEqualTo(2)
+ sendOnDisplayConnected(1)
+ sendOnDisplayConnected(2)
+
+ assertThat(pendingDisplay!!.id).isEqualTo(2)
+ }
+
+ @Test
+ fun onPendingDisplay_enable_displayEnabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.enable()
+
+ verify(displayManager).enableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_enableBySysui_disabledBySomeoneElse_pendingDisplayStillIgnored() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.enable()
+ // to mock the display being really enabled:
+ sendOnDisplayAdded(1)
+
+ // Simulate the display being disabled by someone else. Now, sysui will have it in the
+ // "pending displays" list again, but it should be ignored.
+ sendOnDisplayRemoved(1)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onPendingDisplay_ignoredBySysui_enabledDisabledBySomeoneElse_pendingDisplayStillIgnored() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.ignore()
+
+ // to mock the display being enabled and disabled by someone else:
+ sendOnDisplayAdded(1)
+ sendOnDisplayRemoved(1)
+
+ // Sysui already decided to ignore it, so the pending display should be null.
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onPendingDisplay_disable_displayDisabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.disable()
+
+ verify(displayManager).disableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_ignore_pendingDisplayNull() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+ sendOnDisplayConnected(1)
+
+ pendingDisplay!!.ignore()
+
+ assertThat(pendingDisplay).isNull()
+ verify(displayManager, never()).disableConnectedDisplay(eq(1))
+ verify(displayManager, never()).enableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_enabled_pendingDisplayNull() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ assertThat(pendingDisplay).isNotNull()
+
+ setDisplays(1)
+ sendOnDisplayAdded(1)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onPendingDisplay_multipleConnected_oneEnabled_pendingDisplayNotNull() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ sendOnDisplayConnected(2)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ setDisplays(1)
+ sendOnDisplayAdded(1)
+
+ assertThat(pendingDisplay).isNotNull()
+ assertThat(pendingDisplay!!.id).isEqualTo(2)
+
+ setDisplays(1, 2)
+ sendOnDisplayAdded(2)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun pendingDisplay_connectedDisconnectedAndReconnected_expectedPendingDisplayState() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ // Plug the cable
+ sendOnDisplayConnected(1)
+
+ // Enable it
+ assertThat(pendingDisplay).isNotNull()
+ pendingDisplay!!.enable()
+
+ // Enabled
+ verify(displayManager).enableConnectedDisplay(1)
+ setDisplays(1)
+ sendOnDisplayAdded(1)
+
+ // No more pending displays
+ assertThat(pendingDisplay).isNull()
+
+ // Let's disconnect the cable
+ setDisplays()
+ sendOnDisplayRemoved(1)
+ sendOnDisplayDisconnected(1)
+
+ assertThat(pendingDisplay).isNull()
+
+ // Let's reconnect it
+ sendOnDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
}
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
@@ -216,6 +355,23 @@
// Wrapper to capture the displayListener.
private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
val flowValue = collectLastValue(displayRepository.displays)
+ captureAddedRemovedListener()
+ return flowValue
+ }
+
+ private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> {
+ val flowValue = collectLastValue(displayRepository.pendingDisplay)
+ captureAddedRemovedListener()
+ verify(displayManager)
+ .registerDisplayListener(
+ connectedDisplayListener.capture(),
+ eq(testHandler),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+ )
+ return flowValue
+ }
+
+ private fun captureAddedRemovedListener() {
verify(displayManager)
.registerDisplayListener(
displayListener.capture(),
@@ -226,18 +382,20 @@
DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
)
)
- return flowValue
+ }
+ private fun sendOnDisplayAdded(id: Int) {
+ displayListener.value.onDisplayAdded(id)
+ }
+ private fun sendOnDisplayRemoved(id: Int) {
+ displayListener.value.onDisplayRemoved(id)
}
- private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
- val flowValue = collectLastValue(displayRepository.pendingDisplayId)
- verify(displayManager)
- .registerDisplayListener(
- displayListener.capture(),
- eq(testHandler),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
- )
- return flowValue
+ private fun sendOnDisplayDisconnected(id: Int) {
+ connectedDisplayListener.value.onDisplayDisconnected(id)
+ }
+
+ private fun sendOnDisplayConnected(id: Int) {
+ connectedDisplayListener.value.onDisplayConnected(id)
}
private fun setDisplays(displays: List<Display>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index 50617a1..26ee094 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.display.domain.interactor
-import android.hardware.display.DisplayManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -27,12 +26,11 @@
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.createPendingDisplay
import com.android.systemui.display.data.repository.display
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -41,7 +39,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -49,20 +46,15 @@
@SmallTest
class ConnectedDisplayInteractorTest : SysuiTestCase() {
- private val displayManager = mock<DisplayManager>()
private val fakeDisplayRepository = FakeDisplayRepository()
private val fakeKeyguardRepository = FakeKeyguardRepository()
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
- ConnectedDisplayInteractorImpl(
- displayManager,
- fakeKeyguardRepository,
- fakeDisplayRepository
- )
+ ConnectedDisplayInteractorImpl(fakeKeyguardRepository, fakeDisplayRepository)
private val testScope = TestScope(UnconfinedTestDispatcher())
@Before
fun setup() {
- fakeKeyguardRepository.setKeyguardUnlocked(true)
+ fakeKeyguardRepository.setKeyguardShowing(false)
}
@Test
@@ -148,7 +140,7 @@
fun pendingDisplay_propagated() =
testScope.runTest {
val value by lastPendingDisplay()
- val pendingDisplayId = 4
+ val pendingDisplayId = createPendingDisplay()
fakeDisplayRepository.emit(pendingDisplayId)
@@ -156,51 +148,29 @@
}
@Test
- fun onPendingDisplay_enable_displayEnabled() =
+ fun onPendingDisplay_keyguardShowing_returnsPendingDisplay() =
testScope.runTest {
+ fakeKeyguardRepository.setKeyguardShowing(true)
val pendingDisplay by lastPendingDisplay()
- fakeDisplayRepository.emit(1)
- pendingDisplay!!.enable()
-
- Mockito.verify(displayManager).enableConnectedDisplay(eq(1))
- }
-
- @Test
- fun onPendingDisplay_disable_displayDisabled() =
- testScope.runTest {
- val pendingDisplay by lastPendingDisplay()
-
- fakeDisplayRepository.emit(1)
- pendingDisplay!!.disable()
-
- Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
- }
-
- @Test
- fun onPendingDisplay_keyguardUnlocked_returnsPendingDisplay() =
- testScope.runTest {
- fakeKeyguardRepository.setKeyguardUnlocked(false)
- val pendingDisplay by lastPendingDisplay()
-
- fakeDisplayRepository.emit(1)
+ fakeDisplayRepository.emit(createPendingDisplay())
assertThat(pendingDisplay).isNull()
- fakeKeyguardRepository.setKeyguardUnlocked(true)
+ fakeKeyguardRepository.setKeyguardShowing(false)
assertThat(pendingDisplay).isNotNull()
}
@Test
- fun onPendingDisplay_keyguardLocked_returnsNull() =
+ fun onPendingDisplay_keyguardShowing_returnsNull() =
testScope.runTest {
- fakeKeyguardRepository.setKeyguardUnlocked(true)
+ fakeKeyguardRepository.setKeyguardShowing(false)
val pendingDisplay by lastPendingDisplay()
- fakeDisplayRepository.emit(1)
+ fakeDisplayRepository.emit(createPendingDisplay())
assertThat(pendingDisplay).isNotNull()
- fakeKeyguardRepository.setKeyguardUnlocked(false)
+ fakeKeyguardRepository.setKeyguardShowing(true)
assertThat(pendingDisplay).isNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index 7059647..46f7582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -68,6 +68,28 @@
verify(onStartMirroringCallback, never()).onClick(any())
}
+ @Test
+ fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
+ dialog.show()
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ dialog.cancel()
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
+ @Test
+ fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
+ dialog.show()
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ dialog.dismiss()
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
@After
fun teardown() {
if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 57307fc..781ad6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -3,9 +3,10 @@
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
-import android.testing.AndroidTestingRunner
import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
@@ -29,8 +30,9 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
index 9f534ef..21192fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -15,8 +15,9 @@
*/
package com.android.systemui.dreams
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -29,8 +30,9 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class DreamOverlayCallbackControllerTest : SysuiTestCase() {
@Mock private lateinit var callback: DreamOverlayCallbackController.Callback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 8786520..7c36642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -28,21 +28,22 @@
import android.content.res.Resources;
import android.graphics.Region;
import android.os.Handler;
-import android.testing.AndroidTestingRunner;
import android.view.AttachedSurfaceControl;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.complication.ComplicationHostViewController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
import com.android.systemui.statusbar.BlurUtils;
import org.junit.Before;
@@ -52,8 +53,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
private static final int MAX_BURN_IN_OFFSET = 20;
private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
index 25d4c47..be7638e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
@@ -22,10 +22,11 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
@@ -37,8 +38,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
@Mock
NotificationListener mNotificationListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index d99f0da..8379f73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -37,7 +37,6 @@
import android.service.dreams.IDreamOverlayCallback;
import android.service.dreams.IDreamOverlayClient;
import android.service.dreams.IDreamOverlayClientCallback;
-import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -45,10 +44,12 @@
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.complication.ComplicationLayoutEngine;
import com.android.systemui.dreams.complication.HideComplicationTouchHandler;
@@ -71,8 +72,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class DreamOverlayServiceTest extends SysuiTestCase {
private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package",
"lowlight");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 44a78ac..2ef227c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -26,10 +26,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.complication.Complication;
import com.android.systemui.flags.FeatureFlags;
@@ -48,8 +48,9 @@
import java.util.Collection;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class DreamOverlayStateControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStateController.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
index a78886f..12cb332 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
@@ -21,10 +21,10 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -36,8 +36,9 @@
import java.util.List;
import java.util.concurrent.Executor;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase {
@Mock
DreamOverlayStatusBarItemsProvider.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 4e74f451..af2dab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -41,12 +41,13 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
import android.view.View;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.FakeLogBuffer;
@@ -71,8 +72,9 @@
import java.util.Optional;
import java.util.concurrent.Executor;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
private static final String NOTIFICATION_INDICATOR_FORMATTER_STRING =
"{count, plural, =1 {# notification} other {# notifications}}";
@@ -92,8 +94,6 @@
@Mock
AlarmManager mAlarmManager;
@Mock
- AlarmManager.AlarmClockInfo mAlarmClockInfo;
- @Mock
NextAlarmController mNextAlarmController;
@Mock
DateFormatUtil mDateFormatUtil;
@@ -203,8 +203,9 @@
@Test
public void testOnViewAttachedShowsAlarmIconWhenAlarmExists() {
- when(mAlarmClockInfo.getTriggerTime()).thenReturn(1L);
- when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(mAlarmClockInfo);
+ final AlarmManager.AlarmClockInfo alarmClockInfo =
+ new AlarmManager.AlarmClockInfo(1L, null);
+ when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(alarmClockInfo);
mController.onViewAttached();
verify(mView).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
index eed4dbc..d32788d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
@@ -23,13 +23,14 @@
import static org.mockito.Mockito.when;
import android.os.Handler;
-import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
import android.view.View;
import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.complication.Complication;
import com.android.systemui.dreams.DreamOverlayStateController;
@@ -48,8 +49,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class HideComplicationTouchHandlerTest extends SysuiTestCase {
private static final int RESTORE_TIMEOUT = 1000;
private static final int HIDE_DELAY = 500;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index 6a17889..4a7700f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -23,10 +23,10 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
@@ -41,8 +41,9 @@
import kotlinx.coroutines.CoroutineScope;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class AssistantAttentionConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 68c7965..cd2efde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -25,12 +25,13 @@
import static org.mockito.Mockito.when;
import android.app.DreamManager;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.condition.Condition;
@@ -43,8 +44,9 @@
import kotlinx.coroutines.CoroutineScope;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class DreamConditionTest extends SysuiTestCase {
@Mock
Condition.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 3f9b198..90076fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -31,16 +31,17 @@
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.graphics.Region;
-import android.testing.AndroidTestingRunner;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.VelocityTracker;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dreams.touch.scrim.ScrimController;
@@ -56,6 +57,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -63,8 +65,9 @@
import java.util.Collections;
import java.util.Optional;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@Mock
CentralSurfaces mCentralSurfaces;
@@ -107,6 +110,12 @@
@Mock
LockPatternUtils mLockPatternUtils;
+ @Mock
+ Region mRegion;
+
+ @Captor
+ ArgumentCaptor<Rect> mRectCaptor;
+
FakeUserTracker mUserTracker;
private static final float TOUCH_REGION = .3f;
@@ -153,10 +162,10 @@
*/
@Test
public void testSessionStart() {
- final Region region = Region.obtain();
- mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, region);
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion);
- final Rect bounds = region.getBounds();
+ verify(mRegion).op(mRectCaptor.capture(), eq(Region.Op.UNION));
+ final Rect bounds = mRectCaptor.getValue();
final Rect expected = new Rect();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
index 2b98214..ff6d97d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
@@ -21,12 +21,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
import android.view.GestureDetector;
import android.view.MotionEvent;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -42,8 +43,9 @@
import java.util.Optional;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ShadeTouchHandlerTest extends SysuiTestCase {
@Mock
CentralSurfaces mCentralSurfaces;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
index 79c535a..da39381 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -22,10 +22,11 @@
import static org.mockito.Mockito.verify;
import android.os.PowerManager;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -37,8 +38,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class BouncerlessScrimControllerTest extends SysuiTestCase {
@Mock
BouncerlessScrimController.Callback mCallback;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
index ac9822d..81f6fe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
@@ -22,10 +22,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -38,8 +38,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@RoboPilotTest
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ScrimManagerTest extends SysuiTestCase {
@Mock
ScrimController mBouncerlessScrimController;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 6f51d1b..2ac625d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -30,20 +30,24 @@
}
}
+/** Creates a mock [DisplayRepository.PendingDisplay]. */
+fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
+ mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
+
/** Fake [DisplayRepository] implementation for testing. */
class FakeDisplayRepository : DisplayRepository {
private val flow = MutableSharedFlow<Set<Display>>()
- private val pendingDisplayFlow = MutableSharedFlow<Int?>()
+ private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
- /** Emits [value] as [pendingDisplayId] flow value. */
- suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
+ /** Emits [value] as [pendingDisplay] flow value. */
+ suspend fun emit(value: DisplayRepository.PendingDisplay?) = pendingDisplayFlow.emit(value)
override val displays: Flow<Set<Display>>
get() = flow
- override val pendingDisplayId: Flow<Int?>
+ override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
}
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
index 7a7d376..6526c78 100644
--- a/services/companion/java/com/android/server/companion/virtual/Android.bp
+++ b/services/companion/java/com/android/server/companion/virtual/Android.bp
@@ -1,9 +1,6 @@
java_aconfig_library {
name: "virtualdevice_flags_lib",
aconfig_declarations: "virtualdevice_flags",
- static_libs: [
- "android.companion.virtual.flags-aconfig-java",
- ],
}
aconfig_declarations {
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index 6675568..ce4067c 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -19,6 +19,7 @@
import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -146,6 +147,7 @@
*
* @param runningUids uids of the application running on the virtual display
*/
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
public void blockCameraAccessIfNeeded(Set<Integer> runningUids) {
synchronized (mLock) {
for (int i = 0; i < mAppsToBlockOnVirtualDevice.size(); i++) {
@@ -181,6 +183,7 @@
}
@Override
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
synchronized (mLock) {
InjectionSessionData data = mPackageToSessionData.get(packageName);
@@ -243,6 +246,7 @@
/**
* Turns on blocking for a particular camera and package.
*/
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
private void startBlocking(String packageName, String cameraId) {
try {
Slog.d(
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index cf6092e..9e7b897 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -136,7 +136,8 @@
@Nullable private final SecureWindowCallback mSecureWindowCallback;
@Nullable private final Set<String> mDisplayCategories;
- private final boolean mShowTasksInHostDeviceRecents;
+ @GuardedBy("mGenericWindowPolicyControllerLock")
+ private boolean mShowTasksInHostDeviceRecents;
/**
* Creates a window policy controller that is generic to the different use cases of virtual
@@ -204,6 +205,15 @@
mDisplayId = displayId;
}
+ /**
+ * Set whether to show activities in recents on the host device.
+ */
+ public void setShowInHostDeviceRecents(boolean showInHostDeviceRecents) {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mShowTasksInHostDeviceRecents = showInHostDeviceRecents;
+ }
+ }
+
/** Register a listener for running applications changes. */
public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
synchronized (mGenericWindowPolicyControllerLock) {
@@ -347,7 +357,9 @@
@Override
public boolean canShowTasksInHostDeviceRecents() {
- return mShowTasksInHostDeviceRecents;
+ synchronized (mGenericWindowPolicyControllerLock) {
+ return mShowTasksInHostDeviceRecents;
+ }
}
@Override
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 307f7bf..eeaa423 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -16,6 +16,8 @@
package com.android.server.companion.virtual;
+import static android.text.TextUtils.formatSimple;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StringDef;
@@ -350,7 +352,7 @@
}
private static String createPhys(@PhysType String type) {
- return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
+ return formatSimple("virtual%s:%d", type, sNextPhysId.getAndIncrement());
}
private void setUniqueIdAssociation(int displayId, String phys) {
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index a831401..e9241dd 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -323,8 +323,5 @@
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 f23fe2a..56afeb1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -19,12 +19,15 @@
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -41,6 +44,7 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.AttributionSource;
@@ -78,12 +82,15 @@
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
@@ -93,7 +100,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -128,8 +134,8 @@
private final int mOwnerUid;
private final VirtualDeviceLog mVirtualDeviceLog;
private final String mOwnerPackageName;
- private int mDeviceId;
- private @Nullable String mPersistentDeviceId;
+ private final int mDeviceId;
+ private @Nullable final String mPersistentDeviceId;
// Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
// Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
// 1. After display is created the window manager calls into VDM during construction
@@ -145,6 +151,8 @@
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
@GuardedBy("mVirtualDeviceLock")
+ private final SparseIntArray mDevicePolicies;
+ @GuardedBy("mVirtualDeviceLock")
private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
private final IVirtualDeviceActivityListener mActivityListener;
private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
@@ -152,7 +160,7 @@
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
- private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
+ private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
// The default setting for showing the pointer on new displays.
@GuardedBy("mVirtualDeviceLock")
private boolean mDefaultShowPointerIcon = true;
@@ -259,6 +267,7 @@
mDeviceId = deviceId;
mAppToken = token;
mParams = params;
+ mDevicePolicies = params.getDevicePolicies();
mDisplayManager = displayManager;
if (inputController == null) {
mInputController = new InputController(
@@ -321,7 +330,13 @@
/** Returns the policy specified for this policy type */
public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
@VirtualDeviceParams.PolicyType int policyType) {
- return mParams.getDevicePolicy(policyType);
+ if (Flags.dynamicPolicy()) {
+ synchronized (mVirtualDeviceLock) {
+ return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
+ }
+ } else {
+ return mParams.getDevicePolicy(policyType);
+ }
}
/** Returns device-specific audio session id for playback. */
@@ -410,15 +425,13 @@
public void close() {
super.close_enforcePermission();
// Remove about-to-be-closed virtual device from the service before butchering it.
- boolean removed = mService.removeVirtualDevice(mDeviceId);
- mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid);
- mDeviceId = Context.DEVICE_ID_INVALID;
-
- // Device is already closed.
- if (!removed) {
+ if (!mService.removeVirtualDevice(mDeviceId)) {
+ // Device is already closed.
return;
}
+ mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid);
+
final long ident = Binder.clearCallingIdentity();
try {
VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
@@ -460,6 +473,7 @@
}
@Override
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
mCameraAccessController.blockCameraAccessIfNeeded(runningUids);
mRunningAppsChangedCallback.accept(runningUids);
@@ -507,6 +521,27 @@
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+ @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+ super.setDevicePolicy_enforcePermission();
+ switch (policyType) {
+ case POLICY_TYPE_RECENTS:
+ synchronized (mVirtualDeviceLock) {
+ mDevicePolicies.put(policyType, devicePolicy);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ mVirtualDisplays.valueAt(i).getWindowPolicyController()
+ .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ }
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Device policy " + policyType
+ + " cannot be changed at runtime. ");
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
super.createVirtualDpad_enforcePermission();
Objects.requireNonNull(config);
@@ -718,11 +753,9 @@
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
- for (int i = 0; i < mVirtualDisplays.size(); i++) {
- final int displayId = mVirtualDisplays.keyAt(i);
- mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
- }
}
+ getDisplayIds().forEach(
+ displayId -> mInputController.setShowPointerIcon(showPointerIcon, displayId));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -786,6 +819,7 @@
mParams.dump(fout, " ");
fout.println(" mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
+ fout.println(" mDevicePolicies: " + mDevicePolicies);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
fout.println(" " + mVirtualDisplays.keyAt(i));
}
@@ -812,9 +846,7 @@
this::onSecureWindowShown,
this::shouldInterceptIntent,
displayCategories,
- mParams.getDevicePolicy(
- VirtualDeviceParams.POLICY_TYPE_RECENTS)
- == VirtualDeviceParams.DEVICE_POLICY_DEFAULT);
+ mParams.getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT);
gwpc.registerRunningAppsChangedListener(/* listener= */ this);
return gwpc;
}
@@ -830,6 +862,7 @@
this, gwpc, packageName);
gwpc.setDisplayId(displayId);
+ boolean showPointer;
synchronized (mVirtualDeviceLock) {
if (mVirtualDisplays.contains(displayId)) {
gwpc.unregisterRunningAppsChangedListener(this);
@@ -839,11 +872,12 @@
PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+ showPointer = mDefaultShowPointerIcon;
}
final long token = Binder.clearCallingIdentity();
try {
- mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
+ mInputController.setShowPointerIcon(showPointer, displayId);
mInputController.setPointerAcceleration(1f, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
@@ -869,6 +903,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
Intent intent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
@@ -950,6 +985,7 @@
}
}
+ @SuppressWarnings("AndroidFrameworkRequiresPermission")
private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
if (mContext.checkCallingPermission(android.Manifest.permission.INJECT_EVENTS)
== PackageManager.PERMISSION_GRANTED) {
@@ -1031,8 +1067,8 @@
*/
void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
Looper looper) {
- ArrayList<Integer> displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
- if (displayIdsForUid.isEmpty()) {
+ IntArray displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
+ if (displayIdsForUid.size() == 0) {
return;
}
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
@@ -1045,8 +1081,8 @@
}
}
- private ArrayList<Integer> getDisplayIdsWhereUidIsRunning(int uid) {
- ArrayList<Integer> displayIdsForUid = new ArrayList<>();
+ private IntArray getDisplayIdsWhereUidIsRunning(int uid) {
+ IntArray displayIdsForUid = new IntArray();
synchronized (mVirtualDeviceLock) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index a135878..4da9298 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
import android.companion.AssociationInfo;
@@ -96,6 +97,7 @@
private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener =
new CompanionDeviceManager.OnAssociationsChangedListener() {
@Override
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) {
syncVirtualDevicesToCdmAssociations(associations);
}
@@ -241,6 +243,7 @@
return true;
}
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) {
Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>();
synchronized (mVirtualDeviceManagerLock) {
@@ -266,6 +269,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
private void registerCdmAssociationListener() {
final CompanionDeviceManager cdm = getContext().getSystemService(
CompanionDeviceManager.class);
@@ -273,6 +277,7 @@
mCdmAssociationListener);
}
+ @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
private void unregisterCdmAssociationListener() {
final CompanionDeviceManager cdm = getContext().getSystemService(
CompanionDeviceManager.class);
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 557e4ac..838aae8 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1409,4 +1409,10 @@
*/
public abstract boolean isPackageQuarantined(@NonNull String packageName,
@UserIdInt int userId);
+
+ /**
+ * Return a list of all historical install sessions for the given user.
+ */
+ public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(
+ int userId);
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 60d323a..2249607 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -32,6 +32,7 @@
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.RequiresNoPermission;
+import android.annotation.SuppressLint;
import android.app.StatsManager;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -83,6 +84,7 @@
import android.os.health.HealthStatsWriter;
import android.os.health.UidHealthStats;
import android.power.PowerStatsInternal;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
@@ -175,6 +177,7 @@
.replaceWith("?");
private static final int MAX_LOW_POWER_STATS_SIZE = 32768;
private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
+ private static final String MIN_CONSUMED_POWER_THRESHOLD_KEY = "min_consumed_power_threshold";
private static final String EMPTY = "Empty";
private final HandlerThread mHandlerThread;
@@ -863,12 +866,17 @@
final BatteryUsageStats bus;
switch (atomTag) {
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+ @SuppressLint("MissingPermission")
+ final double minConsumedPowerThreshold =
+ DeviceConfig.getFloat(DeviceConfig.NAMESPACE_BATTERY_STATS,
+ MIN_CONSUMED_POWER_THRESHOLD_KEY, 0);
final BatteryUsageStatsQuery querySinceReset =
new BatteryUsageStatsQuery.Builder()
.setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.includePowerModels()
+ .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
.build();
bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 9219623..095d907 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1975,9 +1975,10 @@
return mProviderMap.dumpProviderProto(fd, pw, name, args);
}
- private Boolean isAuthorityRedirectedForCloneProfileCached(String auth) {
+ private boolean isAuthorityRedirectedForCloneProfileCached(String auth) {
if (mCloneProfileAuthorityRedirectionCache.containsKey(auth)) {
- return mCloneProfileAuthorityRedirectionCache.get(auth);
+ final Boolean retVal = mCloneProfileAuthorityRedirectionCache.get(auth);
+ return retVal == null ? false : retVal.booleanValue();
} else {
boolean isAuthRedirected = isAuthorityRedirectedForCloneProfile(auth);
mCloneProfileAuthorityRedirectionCache.put(auth, isAuthRedirected);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 36b8283..bbda952 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -117,20 +117,21 @@
// All the aconfig flags under the listed DeviceConfig scopes will be synced to native level.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
- "core_experiments_team_internal",
- "camera_platform",
- "power",
- "vibrator",
- "haptics",
- "text",
- "arc_next",
- "test_suites",
- "hardware_backed_security_mainline",
- "threadnetwork",
- "media_solutions",
- "responsible_apis",
- "rust",
- "pixel_biometrics",
+ "biometrics_framework",
+ "core_experiments_team_internal",
+ "camera_platform",
+ "power",
+ "vibrator",
+ "haptics",
+ "text",
+ "arc_next",
+ "test_suites",
+ "hardware_backed_security_mainline",
+ "threadnetwork",
+ "media_solutions",
+ "responsible_apis",
+ "rust",
+ "pixel_biometrics",
};
private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3d5b7fa..0aa9cc1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -140,6 +140,7 @@
import android.media.VolumePolicy;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
@@ -12050,6 +12051,49 @@
}
}
+ /**
+ * Update {@link AudioMixingRule}-s for already registered {@link AudioMix}-es.
+ *
+ * @param mixesToUpdate - array of already registered {@link AudioMix}-es to update.
+ * @param updatedMixingRules - array of {@link AudioMixingRule}-s corresponding to
+ * {@code mixesToUpdate} mixes. The array must be same size as
+ * {@code mixesToUpdate} and i-th {@link AudioMixingRule} must
+ * correspond to i-th {@link AudioMix} from mixesToUpdate array.
+ * @param pcb - {@link IAudioPolicyCallback} corresponding to the registered
+ * {@link AudioPolicy} all {@link AudioMix}-es for {@code mixesToUpdate}
+ * are part of.
+ * @return {@link AudioManager#SUCCESS} iff the mixing rules were updated successfully,
+ * {@link AudioManager#ERROR} otherwise.
+ */
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public int updateMixingRulesForPolicy(
+ @NonNull AudioMix[] mixesToUpdate,
+ @NonNull AudioMixingRule[] updatedMixingRules,
+ @NonNull IAudioPolicyCallback pcb) {
+ super.updateMixingRulesForPolicy_enforcePermission();
+ Objects.requireNonNull(mixesToUpdate);
+ Objects.requireNonNull(updatedMixingRules);
+ Objects.requireNonNull(pcb);
+ if (mixesToUpdate.length != updatedMixingRules.length) {
+ Log.e(TAG, "Provided list of audio mixes to update and corresponding mixing rules "
+ + "have mismatching length (mixesToUpdate.length = " + mixesToUpdate.length
+ + ", updatedMixingRules.length = " + updatedMixingRules.length + ").");
+ return AudioManager.ERROR;
+ }
+ if (DEBUG_AP) {
+ Log.d(TAG, "updateMixingRules for " + pcb.asBinder() + "with mix rules: ");
+ }
+ synchronized (mAudioPolicies) {
+ final AudioPolicyProxy app =
+ checkUpdateForPolicy(pcb, "Cannot add AudioMix in audio policy");
+ if (app == null) {
+ return AudioManager.ERROR;
+ }
+ return app.updateMixingRules(mixesToUpdate, updatedMixingRules) == AudioSystem.SUCCESS
+ ? AudioManager.SUCCESS : AudioManager.ERROR;
+ }
+ }
+
/** see AudioPolicy.setUidDeviceAffinity() */
public int setUidDeviceAffinity(IAudioPolicyCallback pcb, int uid,
@NonNull int[] deviceTypes, @NonNull String[] deviceAddresses) {
@@ -12787,6 +12831,35 @@
}
+ @AudioSystem.AudioSystemError int updateMixingRules(
+ @NonNull AudioMix[] mixesToUpdate,
+ @NonNull AudioMixingRule[] updatedMixingRules) {
+ Objects.requireNonNull(mixesToUpdate);
+ Objects.requireNonNull(updatedMixingRules);
+
+ if (mixesToUpdate.length != updatedMixingRules.length) {
+ Log.e(TAG, "Provided list of audio mixes to update and corresponding mixing rules "
+ + "have mismatching length (mixesToUpdate.length = " + mixesToUpdate.length
+ + ", updatedMixingRules.length = " + updatedMixingRules.length + ").");
+ return AudioSystem.BAD_VALUE;
+ }
+
+ synchronized (mMixes) {
+ try (SafeCloseable unused = ClearCallingIdentityContext.create()) {
+ int ret = mAudioSystem.updateMixingRules(mixesToUpdate, updatedMixingRules);
+ if (ret == AudioSystem.SUCCESS) {
+ for (int i = 0; i < mixesToUpdate.length; i++) {
+ AudioMix audioMixToUpdate = mixesToUpdate[i];
+ AudioMixingRule audioMixingRule = updatedMixingRules[i];
+ mMixes.stream().filter(audioMixToUpdate::equals).findAny().ifPresent(
+ mix -> mix.setAudioMixingRule(audioMixingRule));
+ }
+ }
+ return ret;
+ }
+ }
+ }
+
int setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses) {
final Integer Uid = new Integer(uid);
if (mUidDeviceAffinities.remove(Uid) != null) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index e70b649..4f46dd1 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -26,6 +26,7 @@
import android.media.ISoundDose;
import android.media.ISoundDoseCallback;
import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -601,6 +602,21 @@
}
/**
+ * Update already {@link AudioMixingRule}-s for already registered {@link AudioMix}-es.
+ *
+ * @param mixes - array of registered {@link AudioMix}-es to update.
+ * @param updatedMixingRules - array of {@link AudioMixingRule}-s corresponding to
+ * {@code mixesToUpdate} mixes. The array must be same size as
+ * {@code mixesToUpdate} and i-th {@link AudioMixingRule} must
+ * correspond to i-th {@link AudioMix} from mixesToUpdate array.
+ */
+ public int updateMixingRules(@NonNull AudioMix[] mixes,
+ @NonNull AudioMixingRule[] updatedMixingRules) {
+ invalidateRoutingCache();
+ return AudioSystem.updatePolicyMixes(mixes, updatedMixingRules);
+ }
+
+ /**
* Same as {@link AudioSystem#setUidDeviceAffinities(int, int[], String[])}
* @param uid
* @param types
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 0d6635d..2d763bc 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -407,6 +407,12 @@
}
}
+ void stop() {
+ if (mEglContext != null && mEglDisplay != null) {
+ EGL14.eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ }
+
/**
* Draws an animation frame showing the color fade activated at the
* specified level.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e6b5057..40dbabf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -3536,7 +3536,8 @@
DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
int displayId, int displayState) {
- return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+ return new DisplayPowerState(blanker, colorFade, displayId, displayState,
+ new Handler(/*async=*/ true));
}
DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 252c29d..051c886 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -319,8 +319,6 @@
// Must only be accessed on the handler thread.
private DisplayPowerState mPowerState;
-
-
// The currently active screen on unblocker. This field is non-null whenever
// we are waiting for a callback to release it and unblock the screen.
private ScreenOnUnblocker mPendingScreenOnUnblocker;
@@ -2871,7 +2869,8 @@
DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
int displayId, int displayState) {
- return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+ return new DisplayPowerState(blanker, colorFade, displayId, displayState,
+ new Handler(/*async=*/ true));
}
DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 2c257a1..85c6a6d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -74,8 +74,9 @@
private volatile boolean mStopped;
DisplayPowerState(
- DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
- mHandler = new Handler(true /*async*/);
+ DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState,
+ Handler handler) {
+ mHandler = handler;
mChoreographer = Choreographer.getInstance();
mBlanker = blanker;
mColorFade = colorFade;
@@ -317,6 +318,7 @@
mStopped = true;
mPhotonicModulator.interrupt();
dismissColorFade();
+ stopColorFade();
mCleanListener = null;
mHandler.removeCallbacksAndMessages(null);
}
@@ -376,6 +378,11 @@
}
}
+ // Clears up color fade resources.
+ private void stopColorFade() {
+ if (mColorFade != null) mColorFade.stop();
+ }
+
private final Runnable mScreenUpdateRunnable = new Runnable() {
@Override
public void run() {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 1cfc7d7..69a6c13 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -33,6 +33,7 @@
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_APEX;
+import static android.content.pm.PackageManager.MATCH_ARCHIVED_PACKAGES;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
@@ -1507,7 +1508,7 @@
resolveExternalPackageName(p);
return packageInfo;
- } else if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0
+ } else if ((flags & (MATCH_UNINSTALLED_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0
&& PackageUserStateUtils.isAvailable(state, flags)) {
PackageInfo pi = new PackageInfo();
pi.packageName = ps.getPackageName();
@@ -1516,6 +1517,7 @@
pi.sharedUserId = (sharedUser != null) ? sharedUser.getName() : null;
pi.firstInstallTime = state.getFirstInstallTimeMillis();
pi.lastUpdateTime = ps.getLastUpdateTime();
+ pi.isArchived = isArchived(state);
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = ps.getPackageName();
@@ -1614,7 +1616,7 @@
return generatePackageInfo(ps, flags, userId);
}
- if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) {
+ if (!matchFactoryOnly && (flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
final PackageStateInternal ps = mSettings.getPackage(packageName);
if (ps == null) return null;
if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) {
@@ -1678,9 +1680,11 @@
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
final boolean listApex = (flags & MATCH_APEX) != 0;
final boolean listFactory = (flags & MATCH_FACTORY_ONLY) != 0;
+ // Only list archived apps, not fully uninstalled ones. Other entries are unaffected.
+ final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0;
ArrayList<PackageInfo> list;
- if (listUninstalled) {
+ if (listUninstalled || listArchivedOnly) {
list = new ArrayList<>(mSettings.getPackages().size());
for (PackageStateInternal ps : mSettings.getPackages().values()) {
if (listFactory) {
@@ -1696,6 +1700,9 @@
if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) {
continue;
}
+ if (listArchivedOnly && !isArchived(ps.getUserStateOrDefault(userId))) {
+ continue;
+ }
if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
continue;
}
@@ -1739,6 +1746,13 @@
return new ParceledListSlice<>(list);
}
+ // TODO(b/288142708) Check for userState.isInstalled() here once this bug is fixed.
+ // If an app has isInstalled() == true - it should not be filtered above in any case, currently
+ // it is.
+ private static boolean isArchived(PackageUserStateInternal userState) {
+ return userState.getArchiveState() != null;
+ }
+
public final ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
int sourceUserId, int targetUserId) {
ResolveInfo forwardingResolveInfo = new ResolveInfo();
@@ -2612,7 +2626,7 @@
return UserHandle.getUid(userId, p.getUid());
}
}
- if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+ if ((flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
final PackageStateInternal ps = mSettings.getPackage(packageName);
if (ps != null && PackageStateUtils.isMatch(ps, flags)
&& !shouldFilterApplication(ps, callingUid, userId)) {
@@ -3671,7 +3685,7 @@
ps.getAppId()));
}
}
- if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+ if ((flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
if (PackageStateUtils.isMatch(ps, flags)
&& !shouldFilterApplication(ps, callingUid, userId)) {
return mPermissionManager.getGidsForUid(
@@ -4525,7 +4539,8 @@
flags = updateFlagsForPackage(flags, userId);
enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */,
false /* checkShell */, "get packages holding permissions");
- final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
+ final boolean listUninstalled =
+ (flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0;
ArrayList<PackageInfo> list = new ArrayList<>();
boolean[] tmpBools = new boolean[permissions.length];
diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiverService.java
index 9c31dc9..29f49c8 100644
--- a/services/core/java/com/android/server/pm/PackageArchiverService.java
+++ b/services/core/java/com/android/server/pm/PackageArchiverService.java
@@ -17,17 +17,26 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.IPackageArchiverService;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageArchiver;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Binder;
+import android.os.Bundle;
import android.os.ParcelableException;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -36,6 +45,7 @@
import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -51,7 +61,12 @@
*/
public class PackageArchiverService extends IPackageArchiverService.Stub {
- private static final String TAG = "PackageArchiver";
+ /**
+ * The maximum time granted for an app store to start a foreground service when unarchival
+ * is requested.
+ */
+ // TODO(b/297358628) Make this configurable through a flag.
+ private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000;
private final Context mContext;
private final PackageManagerService mPm;
@@ -82,17 +97,14 @@
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
verifyCaller(providedUid, binderUid);
- PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId);
- verifyInstaller(packageName, ps);
-
- // TODO(b/291569242) Verify that this list is not empty and return failure with
- // intentsender
- List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
- ps.getPackageName(),
- new UserHandle(userId));
-
- // TODO(b/282952870) Bug: should happen after the uninstall completes successfully
- storeArchiveState(ps, mainActivities, userId);
+ ArchiveState archiveState;
+ try {
+ archiveState = createArchiveState(packageName, userId);
+ // TODO(b/282952870) Should be reverted if uninstall fails/cancels
+ storeArchiveState(packageName, archiveState, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new ParcelableException(e);
+ }
// TODO(b/278553670) Add special strings for the delete dialog
mPm.mInstallerService.uninstall(
@@ -100,25 +112,154 @@
callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
}
- private static void verifyInstaller(String packageName, PackageStateInternal ps) {
- if (ps.getInstallSource().mUpdateOwnerPackageName == null
- && ps.getInstallSource().mInstallerPackageName == null) {
+ private ArchiveState createArchiveState(String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
+ PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
+ Binder.getCallingUid(), userId);
+ String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
+ if (responsibleInstallerPackage == null) {
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("No installer found to archive app %s.",
+ packageName));
+ }
+
+ List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+ List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
+ for (int i = 0; i < mainActivities.size(); i++) {
+ // TODO(b/278553670) Extract and store launcher icons
+ ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+ mainActivities.get(i).getLabel().toString(),
+ Path.of("/TODO"), null);
+ archiveActivityInfos.add(activityInfo);
+ }
+
+ return new ArchiveState(archiveActivityInfos, responsibleInstallerPackage);
+ }
+
+ @Override
+ public void requestUnarchive(
+ @NonNull String packageName,
+ @NonNull String callerPackageName,
+ @NonNull UserHandle userHandle) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(userHandle);
+
+ Computer snapshot = mPm.snapshotComputer();
+ int userId = userHandle.getIdentifier();
+ int binderUid = Binder.getCallingUid();
+ int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+ snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
+ "unarchiveApp");
+ verifyCaller(providedUid, binderUid);
+ PackageStateInternal ps;
+ try {
+ ps = getPackageState(packageName, snapshot, binderUid, userId);
+ verifyArchived(ps, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new ParcelableException(e);
+ }
+ String installerPackage = getResponsibleInstallerPackage(ps);
+ if (installerPackage == null) {
throw new ParcelableException(
new PackageManager.NameNotFoundException(
- TextUtils.formatSimple("No installer found to archive app %s.",
+ TextUtils.formatSimple("No installer found to unarchive app %s.",
packageName)));
}
+
+ mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
+ }
+
+ private void verifyArchived(PackageStateInternal ps, int userId)
+ throws PackageManager.NameNotFoundException {
+ PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ // TODO(b/288142708) Check for isInstalled false here too.
+ if (userState.getArchiveState() == null) {
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s is not currently archived.",
+ ps.getPackageName()));
+ }
+ }
+
+ @RequiresPermission(
+ allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+ android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+ android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+ conditional = true)
+ private void unarchiveInternal(String packageName, UserHandle userHandle,
+ String installerPackage) {
+ int userId = userHandle.getIdentifier();
+ Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
+ unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
+ unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS,
+ userId == UserHandle.USER_ALL);
+ unarchiveIntent.setPackage(installerPackage);
+
+ // If the unarchival is requested for all users, the current user is used for unarchival.
+ UserHandle userForUnarchival = userId == UserHandle.USER_ALL
+ ? UserHandle.of(mPm.mUserManager.getCurrentUserId())
+ : userHandle;
+ mContext.sendOrderedBroadcastAsUser(
+ unarchiveIntent,
+ userForUnarchival,
+ /* receiverPermission = */ null,
+ AppOpsManager.OP_NONE,
+ createUnarchiveOptions(),
+ /* resultReceiver= */ null,
+ /* scheduler= */ null,
+ /* initialCode= */ 0,
+ /* initialData= */ null,
+ /* initialExtras= */ null);
+ }
+
+ private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps,
+ int userId) throws PackageManager.NameNotFoundException {
+ List<LauncherActivityInfo> mainActivities =
+ Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
+ ps.getPackageName(),
+ new UserHandle(userId)));
+ if (mainActivities.isEmpty()) {
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("The app %s does not have a main activity.",
+ ps.getPackageName()));
+ }
+
+ return mainActivities;
+ }
+
+ @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+ android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+ android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
+ private Bundle createUnarchiveOptions() {
+ BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppAllowlist(getUnarchiveForegroundTimeout(),
+ TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ REASON_PACKAGE_UNARCHIVE, "");
+ return options.toBundle();
+ }
+
+ private static int getUnarchiveForegroundTimeout() {
+ return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
+ }
+
+ private String getResponsibleInstallerPackage(PackageStateInternal ps) {
+ return ps.getInstallSource().mUpdateOwnerPackageName == null
+ ? ps.getInstallSource().mInstallerPackageName
+ : ps.getInstallSource().mUpdateOwnerPackageName;
}
@NonNull
private static PackageStateInternal getPackageState(String packageName,
- Computer snapshot, int callingUid, int userId) {
+ Computer snapshot, int callingUid, int userId)
+ throws PackageManager.NameNotFoundException {
PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
userId);
if (ps == null) {
- throw new ParcelableException(
- new PackageManager.NameNotFoundException(
- TextUtils.formatSimple("Package %s not found.", packageName)));
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName));
}
return ps;
}
@@ -130,38 +271,25 @@
return mLauncherApps;
}
- private void storeArchiveState(PackageStateInternal ps,
- List<LauncherActivityInfo> mainActivities, int userId) {
- List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
- for (int i = 0; i < mainActivities.size(); i++) {
- // TODO(b/278553670) Extract and store launcher icons
- ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
- mainActivities.get(i).getLabel().toString(),
- Path.of("/TODO"), null);
- activityInfos.add(activityInfo);
- }
-
- InstallSource installSource = ps.getInstallSource();
- String installerPackageName = installSource.mUpdateOwnerPackageName != null
- ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
-
+ private void storeArchiveState(String packageName, ArchiveState archiveState, int userId)
+ throws PackageManager.NameNotFoundException {
synchronized (mPm.mLock) {
- PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId);
+ PackageSetting packageSetting = getPackageSettingLocked(packageName, userId);
packageSetting
.modifyUserState(userId)
- .setArchiveState(new ArchiveState(activityInfos, installerPackageName));
+ .setArchiveState(archiveState);
}
}
@NonNull
@GuardedBy("mPm.mLock")
- private PackageSetting getPackageSettingLocked(String packageName, int userId) {
+ private PackageSetting getPackageSettingLocked(String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
// Shouldn't happen, we already verify presence of the package in getPackageState()
if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
- throw new ParcelableException(
- new PackageManager.NameNotFoundException(
- TextUtils.formatSimple("Package %s not found.", packageName)));
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName));
}
return ps;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
new file mode 100644
index 0000000..d40a715
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 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.content.pm.PackageInstaller.PreapprovalDetails;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+
+/**
+ * A historical session object that stores minimal session info.
+ */
+public final class PackageInstallerHistoricalSession {
+ public final int sessionId;
+ public final int userId;
+ private final String mParams;
+ private final long mCreatedMillis;
+
+ private final File mStageDir;
+ private final String mStageCid;
+
+ private final long mUpdatedMillis;
+
+ private final long mCommittedMillis;
+
+ private final int mOriginalInstallerUid;
+
+ private final String mOriginalInstallerPackageName;
+
+ private final int mInstallerUid;
+
+ private final InstallSource mInstallSource;
+
+ private final float mClientProgress;
+
+ private final float mProgress;
+ private final boolean mSealed;
+
+ private final boolean mPreapprovalRequested;
+ private final boolean mCommitted;
+
+ private final boolean mStageDirInUse;
+
+ private final boolean mPermissionsManuallyAccepted;
+
+ private final int mFinalStatus;
+ private final String mFinalMessage;
+
+ private final int mFds;
+ private final int mBridges;
+
+ private final String mPreapprovalDetails;
+ private final int mParentSessionId;
+ private final boolean mDestroyed;
+ private final int[] mChildSessionIds;
+ private final boolean mSessionApplied;
+ private final boolean mSessionReady;
+ private final boolean mSessionFailed;
+ private final int mSessionErrorCode;
+ private final String mSessionErrorMessage;
+
+ PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid,
+ String originalInstallerPackageName, InstallSource installSource, int installerUid,
+ long createdMillis, long updatedMillis, long committedMillis, File stageDir,
+ String stageCid, float clientProgress, float progress, boolean committed,
+ boolean preapprovalRequested, boolean sealed, boolean permissionsManuallyAccepted,
+ boolean stageDirInUse, boolean destroyed, int fds, int bridges, int finalStatus,
+ String finalMessage, SessionParams params, int parentSessionId,
+ int[] childSessionIds, boolean sessionApplied, boolean sessionFailed,
+ boolean sessionReady, int sessionErrorCode, String sessionErrorMessage,
+ PreapprovalDetails preapprovalDetails) {
+ this.sessionId = sessionId;
+ this.userId = userId;
+ this.mOriginalInstallerUid = originalInstallerUid;
+ this.mOriginalInstallerPackageName = originalInstallerPackageName;
+ this.mInstallSource = installSource;
+ this.mInstallerUid = installerUid;
+ this.mCreatedMillis = createdMillis;
+ this.mUpdatedMillis = updatedMillis;
+ this.mCommittedMillis = committedMillis;
+ this.mStageDir = stageDir;
+ this.mStageCid = stageCid;
+ this.mClientProgress = clientProgress;
+ this.mProgress = progress;
+ this.mCommitted = committed;
+ this.mPreapprovalRequested = preapprovalRequested;
+ this.mSealed = sealed;
+ this.mPermissionsManuallyAccepted = permissionsManuallyAccepted;
+ this.mStageDirInUse = stageDirInUse;
+ this.mDestroyed = destroyed;
+ this.mFds = fds;
+ this.mBridges = bridges;
+ this.mFinalStatus = finalStatus;
+ this.mFinalMessage = finalMessage;
+
+ CharArrayWriter writer = new CharArrayWriter();
+ IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ params.dump(pw);
+ this.mParams = writer.toString();
+
+ this.mParentSessionId = parentSessionId;
+ this.mChildSessionIds = childSessionIds;
+ this.mSessionApplied = sessionApplied;
+ this.mSessionFailed = sessionFailed;
+ this.mSessionReady = sessionReady;
+ this.mSessionErrorCode = sessionErrorCode;
+ this.mSessionErrorMessage = sessionErrorMessage;
+ if (preapprovalDetails != null) {
+ this.mPreapprovalDetails = preapprovalDetails.toString();
+ } else {
+ this.mPreapprovalDetails = null;
+ }
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Session " + sessionId + ":");
+ pw.increaseIndent();
+
+ pw.printPair("userId", userId);
+ pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
+ pw.printPair("mOriginalInstallerPackageName", mOriginalInstallerPackageName);
+ pw.printPair("installerPackageName", mInstallSource.mInstallerPackageName);
+ pw.printPair("installInitiatingPackageName", mInstallSource.mInitiatingPackageName);
+ pw.printPair("installOriginatingPackageName", mInstallSource.mOriginatingPackageName);
+ pw.printPair("mInstallerUid", mInstallerUid);
+ pw.printPair("createdMillis", mCreatedMillis);
+ pw.printPair("updatedMillis", mUpdatedMillis);
+ pw.printPair("committedMillis", mCommittedMillis);
+ pw.printPair("stageDir", mStageDir);
+ pw.printPair("stageCid", mStageCid);
+ pw.println();
+
+ pw.print(mParams);
+
+ pw.printPair("mClientProgress", mClientProgress);
+ pw.printPair("mProgress", mProgress);
+ pw.printPair("mCommitted", mCommitted);
+ pw.printPair("mPreapprovalRequested", mPreapprovalRequested);
+ pw.printPair("mSealed", mSealed);
+ pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
+ pw.printPair("mStageDirInUse", mStageDirInUse);
+ pw.printPair("mDestroyed", mDestroyed);
+ pw.printPair("mFds", mFds);
+ pw.printPair("mBridges", mBridges);
+ pw.printPair("mFinalStatus", mFinalStatus);
+ pw.printPair("mFinalMessage", mFinalMessage);
+ pw.printPair("mParentSessionId", mParentSessionId);
+ pw.printPair("mChildSessionIds", mChildSessionIds);
+ pw.printPair("mSessionApplied", mSessionApplied);
+ pw.printPair("mSessionFailed", mSessionFailed);
+ pw.printPair("mSessionReady", mSessionReady);
+ pw.printPair("mSessionErrorCode", mSessionErrorCode);
+ pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
+ pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+ pw.println();
+
+ pw.decreaseIndent();
+ }
+
+ /**
+ * Generates a {@link SessionInfo} object.
+ */
+ public SessionInfo generateInfo() {
+ final SessionInfo info = new SessionInfo();
+ info.sessionId = sessionId;
+ info.userId = userId;
+ info.installerPackageName = mInstallSource.mInstallerPackageName;
+ info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
+ info.progress = mProgress;
+ info.sealed = mSealed;
+ info.isCommitted = mCommitted;
+ info.isPreapprovalRequested = mPreapprovalRequested;
+
+ info.parentSessionId = mParentSessionId;
+ info.childSessionIds = mChildSessionIds;
+ info.isSessionApplied = mSessionApplied;
+ info.isSessionReady = mSessionReady;
+ info.isSessionFailed = mSessionFailed;
+ info.setSessionErrorCode(mSessionErrorCode, mSessionErrorMessage);
+ info.createdMillis = mCreatedMillis;
+ info.updatedMillis = mUpdatedMillis;
+ info.installerUid = mInstallerUid;
+ return info;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 10cd51a..e360256 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -111,7 +111,6 @@
import org.xmlpull.v1.XmlPullParserException;
-import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -230,7 +229,7 @@
/** Historical sessions kept around for debugging purposes */
@GuardedBy("mSessions")
- private final List<String> mHistoricalSessions = new ArrayList<>();
+ private final List<PackageInstallerHistoricalSession> mHistoricalSessions = new ArrayList<>();
@GuardedBy("mSessions")
private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray();
@@ -570,14 +569,11 @@
@GuardedBy("mSessions")
private void addHistoricalSessionLocked(PackageInstallerSession session) {
- CharArrayWriter writer = new CharArrayWriter();
- IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- session.dump(pw);
if (mHistoricalSessions.size() > HISTORICAL_SESSIONS_THRESHOLD) {
Slog.d(TAG, "Historical sessions size reaches threshold, clear the oldest");
mHistoricalSessions.subList(0, HISTORICAL_CLEAR_SIZE).clear();
}
- mHistoricalSessions.add(writer.toString());
+ mHistoricalSessions.add(session.createHistoricalSession());
int installerUid = session.getInstallerUid();
// Increment the number of sessions by this installerUid.
@@ -1223,6 +1219,24 @@
return new ParceledListSlice<>(result);
}
+ ParceledListSlice<SessionInfo> getHistoricalSessions(int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final Computer snapshot = mPm.snapshotComputer();
+ snapshot.enforceCrossUserPermission(callingUid, userId, true, false, "getAllSessions");
+
+ final List<SessionInfo> result = new ArrayList<>();
+ synchronized (mSessions) {
+ for (int i = 0; i < mHistoricalSessions.size(); i++) {
+ final PackageInstallerHistoricalSession session = mHistoricalSessions.get(i);
+ if (userId == UserHandle.USER_ALL || session.userId == userId) {
+ result.add(session.generateInfo());
+ }
+ }
+ }
+ result.removeIf(info -> shouldFilterSession(snapshot, callingUid, info));
+ return new ParceledListSlice<>(result);
+ }
+
@Override
public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
IntentSender statusReceiver, int userId) {
@@ -1837,7 +1851,7 @@
pw.increaseIndent();
N = mHistoricalSessions.size();
for (int i = 0; i < N; i++) {
- pw.print(mHistoricalSessions.get(i));
+ mHistoricalSessions.get(i).dump(pw);
pw.println();
}
pw.println();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 1554072..1bdade2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1169,6 +1169,25 @@
}
}
+ PackageInstallerHistoricalSession createHistoricalSession() {
+ final float progress;
+ final float clientProgress;
+ synchronized (mProgressLock) {
+ progress = mProgress;
+ clientProgress = mClientProgress;
+ }
+ synchronized (mLock) {
+ return new PackageInstallerHistoricalSession(sessionId, userId, mOriginalInstallerUid,
+ mOriginalInstallerPackageName, mInstallSource, mInstallerUid, createdMillis,
+ updatedMillis, committedMillis, stageDir, stageCid, clientProgress, progress,
+ isCommitted(), isPreapprovalRequested(), mSealed, mPermissionsManuallyAccepted,
+ mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus,
+ mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(),
+ mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode,
+ mSessionErrorMessage, mPreapprovalDetails);
+ }
+ }
+
/**
* Returns {@code true} if the {@link SessionInfo} object should be produced with potentially
* sensitive data scrubbed from its fields.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7390bbe..bb4fd66 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6976,6 +6976,11 @@
return mDistractingPackageHelper.getDistractingPackageRestrictionsAsUser(snapshot,
packageNames, userId, callingUid);
}
+
+ @Override
+ public ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(int userId) {
+ return mInstallerService.getHistoricalSessions(userId);
+ }
}
private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3e4dd16..c6aba2a 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1743,6 +1743,10 @@
android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, "");
throw new SecurityException("Shortcut package name mismatch");
}
+ final int callingUid = injectBinderCallingUid();
+ if (UserHandle.getUserId(callingUid) != si.getUserId()) {
+ throw new SecurityException("User-ID in shortcut doesn't match the caller");
+ }
}
private void verifyShortcutInfoPackages(
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 27812df..4eceb77 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -802,9 +802,7 @@
// If available for the target user, or trying to match uninstalled packages and it's
// a system app.
return PackageUserStateUtils.isAvailable(state, flags)
- || (pkgSetting.isSystem()
- && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
- || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
+ || (pkgSetting.isSystem() && matchUninstalledOrHidden(flags));
}
private static boolean checkUseInstalledOrHidden(long flags,
@@ -819,9 +817,15 @@
// If available for the target user, or trying to match uninstalled packages and it's
// a system app.
return PackageUserStateUtils.isAvailable(state, flags)
- || (appInfo != null && appInfo.isSystemApp()
- && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
- || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
+ || (appInfo != null && appInfo.isSystemApp() && matchUninstalledOrHidden(flags));
+ }
+
+ private static boolean matchUninstalledOrHidden(long flags) {
+ return (flags
+ & (PackageManager.MATCH_KNOWN_PACKAGES
+ | PackageManager.MATCH_ARCHIVED_PACKAGES
+ | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS))
+ != 0;
}
private static void assignFieldsComponentInfoParsedMainComponent(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b01a89e..fc6b12c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1024,7 +1024,9 @@
}
synchronized (mLock) {
- mAttributions.put(source.getToken(), source);
+ // Change the token for the AttributionSource we're storing, so that we don't store
+ // a strong reference to the original token inside the map itself.
+ mAttributions.put(source.getToken(), source.withDefaultToken());
}
}
@@ -1032,7 +1034,7 @@
synchronized (mLock) {
final AttributionSource cachedSource = mAttributions.get(source.getToken());
if (cachedSource != null) {
- return cachedSource.equals(source);
+ return cachedSource.equalsExceptToken(source);
}
return false;
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index e342453..54f7ebc 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -87,9 +87,10 @@
// still return true if the caller requested MATCH_UNINSTALLED_PACKAGES
final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0;
final boolean matchUninstalled = (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0;
+ final boolean matchArchived = (flags & PackageManager.MATCH_ARCHIVED_PACKAGES) != 0;
return matchAnyUser
|| (state.isInstalled()
- && (!state.isHidden() || matchUninstalled));
+ && (!state.isHidden() || matchUninstalled || matchArchived));
}
public static boolean reportIfDebug(boolean result, long flags) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b3aa09b..63794d5 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -559,6 +559,7 @@
boolean mWakeOnDpadKeyPress;
boolean mWakeOnAssistKeyPress;
boolean mWakeOnBackKeyPress;
+ boolean mSilenceRingerOnSleepKey;
long mWakeUpToLastStateTimeout;
int mSearchKeyBehavior;
ComponentName mSearchKeyTargetActivity;
@@ -1423,6 +1424,15 @@
}
private void sleepRelease(long eventTime) {
+ if (mSilenceRingerOnSleepKey) {
+ TelecomManager telecomManager = getTelecommService();
+ if (telecomManager != null && telecomManager.isRinging()) {
+ telecomManager.silenceRinger();
+ Slog.i(TAG, "sleepRelease() silence ringer");
+ return;
+ }
+ }
+
switch (mShortPressOnSleepBehavior) {
case SHORT_PRESS_SLEEP_GO_TO_SLEEP:
case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
@@ -2347,6 +2357,8 @@
com.android.internal.R.string.config_primaryShortPressTargetActivity));
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+ mSilenceRingerOnSleepKey = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_silenceRingerOnSleepKey);
mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup);
@@ -3342,6 +3354,13 @@
return true;
}
break;
+ case KeyEvent.KEYCODE_DEL:
+ case KeyEvent.KEYCODE_GRAVE:
+ if (firstDown && event.isMetaPressed()) {
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+ injectBackGesture(event.getDownTime());
+ return true;
+ }
case KeyEvent.KEYCODE_DPAD_UP:
if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -3353,9 +3372,14 @@
}
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- enterStageSplitFromRunningApp(true /* leftOrTop */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+ if (firstDown && event.isMetaPressed()) {
+ if (event.isCtrlPressed()) {
+ enterStageSplitFromRunningApp(true /* leftOrTop */);
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+ } else {
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+ injectBackGesture(event.getDownTime());
+ }
return true;
}
break;
@@ -3618,6 +3642,25 @@
return (metaState & KeyEvent.META_META_ON) != 0;
}
+ @SuppressLint("MissingPermission")
+ private void injectBackGesture(long downtime) {
+ // Create and inject down event
+ KeyEvent downEvent = new KeyEvent(downtime, downtime, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+ mInputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+
+ // Create and inject up event
+ KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP);
+ mInputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+ downEvent.recycle();
+ upEvent.recycle();
+ }
+
private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
// First we always handle the home key here, so applications
// can never break it, although if keyguard is on, we do let
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 3a32733..f6fa9f2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -170,10 +170,11 @@
&& mStats.isProcessStateDataAvailable();
final boolean includeVirtualUids = ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0);
+ final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
mStats.getCustomEnergyConsumerNames(), includePowerModels,
- includeProcessStateData);
+ includeProcessStateData, minConsumedPowerThreshold);
// TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration
// of stats sessions to wall-clock adjustments
batteryUsageStatsBuilder.setStatsStartTimestamp(mStats.getStartClockTime());
@@ -307,10 +308,12 @@
final boolean includeProcessStateData = ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
&& mStats.isProcessStateDataAvailable();
+ final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customEnergyConsumerNames, includePowerModels, includeProcessStateData);
+ customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+ minConsumedPowerThreshold);
if (mBatteryUsageStatsStore == null) {
Log.e(TAG, "BatteryUsageStatsStore is unavailable");
return builder.build();
diff --git a/services/core/java/com/android/server/security/Android.bp b/services/core/java/com/android/server/security/Android.bp
new file mode 100644
index 0000000..3f644c44
--- /dev/null
+++ b/services/core/java/com/android/server/security/Android.bp
@@ -0,0 +1,17 @@
+aconfig_declarations {
+ name: "com.android.server.security.flags-aconfig",
+ package: "com.android.server.security",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.server.security.flags-aconfig-java",
+ aconfig_declarations: "com.android.server.security.flags-aconfig",
+}
+
+java_aconfig_library {
+ name: "com.android.server.security.flags-aconfig-java-host",
+ aconfig_declarations: "com.android.server.security.flags-aconfig",
+ host_supported: true,
+ test: true,
+}
diff --git a/services/core/java/com/android/server/security/OWNERS b/services/core/java/com/android/server/security/OWNERS
index 5bcc98b6..f408d7c 100644
--- a/services/core/java/com/android/server/security/OWNERS
+++ b/services/core/java/com/android/server/security/OWNERS
@@ -1,4 +1,6 @@
# Bug component: 36824
+include /core/java/android/security/OWNERS
+
per-file *AttestationVerification* = file:/core/java/android/security/attestationverification/OWNERS
per-file FileIntegrity*.java = victorhsieh@google.com
diff --git a/services/core/java/com/android/server/security/flags.aconfig b/services/core/java/com/android/server/security/flags.aconfig
new file mode 100644
index 0000000..0440989f
--- /dev/null
+++ b/services/core/java/com/android/server/security/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.security"
+
+flag {
+ name: "deprecate_fsv_sig"
+ namespace: "hardware_backed_security"
+ description: "Feature flag for deprecating .fsv_sig"
+ bug: "277916185"
+}
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 3fb845f..e4f9607 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,7 +19,7 @@
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
import android.os.vibrator.persistence.ParsedVibration;
import android.os.vibrator.persistence.VibrationXmlParser;
import android.text.TextUtils;
@@ -107,10 +107,10 @@
* @hide
*/
@Nullable
- static SparseArray<VibrationEffect> loadVibrations(Resources res, Vibrator vibrator)
+ static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo)
throws CustomizationParserException, IOException {
try {
- return loadVibrationsInternal(res, vibrator);
+ return loadVibrationsInternal(res, vibratorInfo);
} catch (VibrationXmlParser.VibrationXmlParserException
| XmlParserException
| XmlPullParserException e) {
@@ -121,7 +121,7 @@
@Nullable
private static SparseArray<VibrationEffect> loadVibrationsInternal(
- Resources res, Vibrator vibrator) throws
+ Resources res, VibratorInfo vibratorInfo) throws
CustomizationParserException,
IOException,
VibrationXmlParser.VibrationXmlParserException,
@@ -175,7 +175,7 @@
throw new CustomizationParserException(
"Unable to parse vibration element for effect " + effectId);
}
- VibrationEffect effect = parsedVibration.resolve(vibrator);
+ VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
if (effect != null) {
if (effect.getDuration() == Long.MAX_VALUE) {
throw new CustomizationParserException(String.format(
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 7c99543..3d89afa 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -21,6 +21,7 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.VibratorInfo;
import android.util.Slog;
import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
@@ -29,8 +30,6 @@
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Set;
/**
* Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
@@ -47,7 +46,7 @@
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
- private final Vibrator mVibrator;
+ private final VibratorInfo mVibratorInfo;
private final boolean mHapticTextHandleEnabled;
// Vibrator effect for haptic feedback during boot when safe mode is enabled.
private final VibrationEffect mSafeModeEnabledVibrationEffect;
@@ -58,25 +57,25 @@
/** @hide */
public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
- this(res, vibrator, loadHapticCustomizations(res, vibrator));
+ this(res, vibrator.getInfo());
+ }
+
+ /** @hide */
+ public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
+ this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
}
/** @hide */
@VisibleForTesting HapticFeedbackVibrationProvider(
Resources res,
- Vibrator vibrator,
+ VibratorInfo vibratorInfo,
@Nullable SparseArray<VibrationEffect> hapticCustomizations) {
- mVibrator = vibrator;
+ mVibratorInfo = vibratorInfo;
mHapticTextHandleEnabled = res.getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
- if (hapticCustomizations != null) {
- // Clean up the customizations to remove vibrations which may not ever be used due to
- // Vibrator properties or other device configurations.
- removeUnsupportedVibrations(hapticCustomizations, vibrator);
- if (hapticCustomizations.size() == 0) {
- hapticCustomizations = null;
- }
+ if (hapticCustomizations != null && hapticCustomizations.size() == 0) {
+ hapticCustomizations = null;
}
mHapticCustomizations = hapticCustomizations;
@@ -257,7 +256,7 @@
if (effectHasCustomization(hapticFeedbackId)) {
return mHapticCustomizations.get(hapticFeedbackId);
}
- if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
+ if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
return VibrationEffect.startComposition()
.addPrimitive(primitiveId, primitiveScale)
.compose();
@@ -270,9 +269,8 @@
if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
}
- if (mVibrator.areAllPrimitivesSupported(
- VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
- VibrationEffect.Composition.PRIMITIVE_TICK)) {
+ if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+ && mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) {
// quiet ramp, short pause, then sharp tick
return VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
@@ -289,27 +287,12 @@
@Nullable
private static SparseArray<VibrationEffect> loadHapticCustomizations(
- Resources res, Vibrator vibrator) {
+ Resources res, VibratorInfo vibratorInfo) {
try {
- return HapticFeedbackCustomization.loadVibrations(res, vibrator);
+ return HapticFeedbackCustomization.loadVibrations(res, vibratorInfo);
} catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
Slog.e(TAG, "Unable to load haptic customizations.", e);
return null;
}
}
-
- private static void removeUnsupportedVibrations(
- SparseArray<VibrationEffect> customizations, Vibrator vibrator) {
- Set<Integer> keysToRemove = new HashSet<>();
- for (int i = 0; i < customizations.size(); i++) {
- int key = customizations.keyAt(i);
- if (!vibrator.areVibrationFeaturesSupported(customizations.get(key))) {
- keysToRemove.add(key);
- }
- }
-
- for (int key : keysToRemove) {
- customizations.remove(key);
- }
- }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index e296c7b..ee3d697 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -28,6 +28,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats;
import android.os.Binder;
@@ -131,6 +132,7 @@
private final Object mLock = new Object();
private final Context mContext;
+ private final Injector mInjector;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
private final VibratorFrameworkStatsLogger mFrameworkStatsLogger;
@@ -162,6 +164,8 @@
@GuardedBy("mLock")
@Nullable private VibratorInfo mCombinedVibratorInfo;
+ @GuardedBy("mLock")
+ @Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -201,6 +205,7 @@
@VisibleForTesting
VibratorManagerService(Context context, Injector injector) {
mContext = context;
+ mInjector = injector;
mHandler = injector.createHandler(Looper.myLooper());
mVibrationSettings = new VibrationSettings(mContext, mHandler);
@@ -393,7 +398,42 @@
@Override // Binder call
public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
- vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+ vibrateWithPermissionCheck(uid, displayId, opPkg, effect, attrs, reason, token);
+ }
+
+ @Override // Binder call
+ public void performHapticFeedback(
+ int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+ IBinder token) {
+ performHapticFeedbackInternal(uid, displayId, opPkg, constant, always, reason, token);
+ }
+
+ /**
+ * An internal-only version of performHapticFeedback that allows the caller access to the
+ * {@link HalVibration}.
+ * The Vibration is only returned if it is ongoing after this method returns.
+ */
+ @VisibleForTesting
+ @Nullable
+ HalVibration performHapticFeedbackInternal(
+ int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+ IBinder token) {
+ HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
+ if (hapticVibrationProvider == null) {
+ Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready.");
+ return null;
+ }
+ VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
+ if (effect == null) {
+ Slog.w(TAG, "performHapticFeedback; vibration absent for effect " + constant);
+ return null;
+ }
+ CombinedVibration combinedVibration = CombinedVibration.createParallel(effect);
+ VibrationAttributes attrs =
+ hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ constant, /* bypassVibrationIntensitySetting= */ always);
+ return vibrateWithoutPermissionCheck(uid, displayId, opPkg, combinedVibration, attrs,
+ "performHapticFeedback: " + reason, token);
}
/**
@@ -403,93 +443,110 @@
*/
@VisibleForTesting
@Nullable
- HalVibration vibrateInternal(int uid, int displayId, String opPkg,
+ HalVibration vibrateWithPermissionCheck(int uid, int displayId, String opPkg,
@NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
String reason, IBinder token) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
try {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
-
- if (token == null) {
- Slog.e(TAG, "token must not be null");
- return null;
- }
- enforceUpdateAppOpsStatsPermission(uid);
- if (!isEffectValid(effect)) {
- return null;
- }
- attrs = fixupVibrationAttributes(attrs, effect);
- // Create Vibration.Stats as close to the received request as possible, for tracking.
- HalVibration vib = new HalVibration(token, effect,
- new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
- fillVibrationFallbacks(vib, effect);
-
- if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
- // Force update of user settings before checking if this vibration effect should
- // be ignored or scaled.
- mVibrationSettings.update();
- }
-
- synchronized (mLock) {
- if (DEBUG) {
- Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
- }
-
- // Check if user settings or DnD is set to ignore this vibration.
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
- // Check if ongoing vibration is more important than this vibration.
- if (vibrationEndInfo == null) {
- vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
- }
-
- // If not ignored so far then try to start this vibration.
- if (vibrationEndInfo == null) {
- final long ident = Binder.clearCallingIdentity();
- try {
- if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.mute();
- vib.stats.reportInterruptedAnotherVibration(
- mCurrentExternalVibration.callerInfo);
- endExternalVibrateLocked(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
- vib.callerInfo),
- /* continueExternalControl= */ false);
- } else if (mCurrentVibration != null) {
- if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
- // Don't cancel the current vibration if it's pipeline-able.
- // Note that if there is a pending next vibration that can't be
- // pipelined, it will have already cancelled the current one, so we
- // don't need to consider it here as well.
- if (DEBUG) {
- Slog.d(TAG, "Pipelining vibration " + vib.id);
- }
- } else {
- vib.stats.reportInterruptedAnotherVibration(
- mCurrentVibration.getVibration().callerInfo);
- mCurrentVibration.notifyCancelled(
- new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
- vib.callerInfo),
- /* immediate= */ false);
- }
- }
- vibrationEndInfo = startVibrationLocked(vib);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- // Ignored or failed to start the vibration, end it and report metrics right away.
- if (vibrationEndInfo != null) {
- endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
- }
- return vib;
- }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.VIBRATE, "vibrate");
+ return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
+ HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg,
+ @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
+ String reason, IBinder token) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason);
+ try {
+ return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private HalVibration vibrateInternal(int uid, int displayId, String opPkg,
+ @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
+ String reason, IBinder token) {
+ if (token == null) {
+ Slog.e(TAG, "token must not be null");
+ return null;
+ }
+ enforceUpdateAppOpsStatsPermission(uid);
+ if (!isEffectValid(effect)) {
+ return null;
+ }
+ attrs = fixupVibrationAttributes(attrs, effect);
+ // Create Vibration.Stats as close to the received request as possible, for tracking.
+ HalVibration vib = new HalVibration(token, effect,
+ new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
+ fillVibrationFallbacks(vib, effect);
+
+ if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+ // Force update of user settings before checking if this vibration effect should
+ // be ignored or scaled.
+ mVibrationSettings.update();
+ }
+
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
+ }
+
+ // Check if user settings or DnD is set to ignore this vibration.
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+
+ // Check if ongoing vibration is more important than this vibration.
+ if (vibrationEndInfo == null) {
+ vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+ }
+
+ // If not ignored so far then try to start this vibration.
+ if (vibrationEndInfo == null) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.mute();
+ vib.stats.reportInterruptedAnotherVibration(
+ mCurrentExternalVibration.callerInfo);
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ vib.callerInfo),
+ /* continueExternalControl= */ false);
+ } else if (mCurrentVibration != null) {
+ if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
+ // Don't cancel the current vibration if it's pipeline-able.
+ // Note that if there is a pending next vibration that can't be
+ // pipelined, it will have already cancelled the current one, so we
+ // don't need to consider it here as well.
+ if (DEBUG) {
+ Slog.d(TAG, "Pipelining vibration " + vib.id);
+ }
+ } else {
+ vib.stats.reportInterruptedAnotherVibration(
+ mCurrentVibration.getVibration().callerInfo);
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ vib.callerInfo),
+ /* immediate= */ false);
+ }
+ }
+ vibrationEndInfo = startVibrationLocked(vib);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // Ignored or failed to start the vibration, end it and report metrics right away.
+ if (vibrationEndInfo != null) {
+ endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
+ }
+ return vib;
+ }
+ }
+
@Override // Binder call
public void cancelVibrate(int usageFilter, IBinder token) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
@@ -1315,6 +1372,11 @@
return new VibratorController(vibratorId, listener);
}
+ HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
+ Resources resources, VibratorInfo vibratorInfo) {
+ return new HapticFeedbackVibrationProvider(resources, vibratorInfo);
+ }
+
void addService(String name, IBinder service) {
ServiceManager.addService(name, service);
}
@@ -1831,6 +1893,22 @@
}
}
+ private HapticFeedbackVibrationProvider getHapticVibrationProvider() {
+ synchronized (mLock) {
+ // Used a cached haptic vibration provider if one exists.
+ if (mHapticFeedbackVibrationProvider != null) {
+ return mHapticFeedbackVibrationProvider;
+ }
+ VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo();
+ if (combinedVibratorInfo == null) {
+ return null;
+ }
+ return mHapticFeedbackVibrationProvider =
+ mInjector.createHapticFeedbackVibrationProvider(
+ mContext.getResources(), combinedVibratorInfo);
+ }
+ }
+
private VibratorInfo getCombinedVibratorInfo() {
synchronized (mLock) {
// Used a cached resolving vibrator if one exists.
@@ -2068,6 +2146,9 @@
if ("cancel".equals(cmd)) {
return runCancel();
}
+ if ("feedback".equals(cmd)) {
+ return runHapticFeedback();
+ }
return handleDefaultCommands(cmd);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -2098,16 +2179,10 @@
// only cancel background vibrations.
IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
: mShellCallbacksToken;
- HalVibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
- SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder);
- if (vib != null && !commonOptions.background) {
- try {
- // Waits for the client vibration to finish, but the VibrationThread may still
- // do cleanup after this.
- vib.waitForEnd();
- } catch (InterruptedException e) {
- }
- }
+ HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(),
+ Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, combined, attrs,
+ commonOptions.description, deathBinder);
+ maybeWaitOnVibration(vib, commonOptions);
}
private int runMono() {
@@ -2155,6 +2230,21 @@
return 0;
}
+ private int runHapticFeedback() {
+ CommonOptions commonOptions = new CommonOptions();
+ int constant = Integer.parseInt(getNextArgRequired());
+
+ IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
+ : mShellCallbacksToken;
+ HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
+ Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, constant,
+ /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
+ deathBinder);
+ maybeWaitOnVibration(vib, commonOptions);
+
+ return 0;
+ }
+
private VibrationEffect nextEffect() {
VibrationEffect.Composition composition = VibrationEffect.startComposition();
String nextArg;
@@ -2364,6 +2454,17 @@
}
}
+ private void maybeWaitOnVibration(HalVibration vib, CommonOptions commonOptions) {
+ if (vib != null && !commonOptions.background) {
+ try {
+ // Waits for the client vibration to finish, but the VibrationThread may still
+ // do cleanup after this.
+ vib.waitForEnd();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
@Override
public void onHelp() {
try (PrintWriter pw = getOutPrintWriter();) {
@@ -2389,6 +2490,10 @@
pw.println(" XML containing a single effect it runs on all vibrators in sync.");
pw.println(" cancel");
pw.println(" Cancels any active vibration");
+ pw.println(" feedback [-f] [-d <description>] <constant>");
+ pw.println(" Performs a haptic feedback with the given constant.");
+ pw.println(" The force (-f) option enables the `always` configuration, which");
+ pw.println(" plays the haptic irrespective of the vibration intensity settings");
pw.println("");
pw.println("Effect commands:");
pw.println(" oneshot [-w delay] [-a] <duration> [<amplitude>]");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 90e67df..582536b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2929,7 +2929,7 @@
reparent(newTaskFrag, position);
}
- private boolean isHomeIntent(Intent intent) {
+ static boolean isHomeIntent(Intent intent) {
return ACTION_MAIN.equals(intent.getAction())
&& (intent.hasCategory(CATEGORY_HOME)
|| intent.hasCategory(CATEGORY_SECONDARY_HOME))
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 1eb56f1..a5b1132 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -55,6 +55,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -77,7 +78,6 @@
private final ActivityTaskManagerService mService;
private final ActivityTaskSupervisor mSupervisor;
- private final RootWindowContainer mRootWindowContainer;
private final Context mServiceContext;
// UserManager cannot be final as it's not ready when this class is instantiated during boot
@@ -110,17 +110,23 @@
TaskFragment mInTaskFragment;
ActivityOptions mActivityOptions;
+ /*
+ * Note that this is just a hint of what the launch display area will be as it is
+ * based only on the information at the early pre-interception stage of starting the
+ * intent. The real launch display area calculated later may be different from this one.
+ */
+ TaskDisplayArea mPresumableLaunchDisplayArea;
+
ActivityStartInterceptor(
ActivityTaskManagerService service, ActivityTaskSupervisor supervisor) {
- this(service, supervisor, service.mRootWindowContainer, service.mContext);
+ this(service, supervisor, service.mContext);
}
@VisibleForTesting
ActivityStartInterceptor(ActivityTaskManagerService service, ActivityTaskSupervisor supervisor,
- RootWindowContainer root, Context context) {
+ Context context) {
mService = service;
mSupervisor = supervisor;
- mRootWindowContainer = root;
mServiceContext = context;
}
@@ -162,7 +168,7 @@
/**
* A helper function to obtain the targeted {@link TaskFragment} during
* {@link #intercept(Intent, ResolveInfo, ActivityInfo, String, Task, TaskFragment, int, int,
- * ActivityOptions)} if any.
+ * ActivityOptions, TaskDisplayArea)} if any.
*/
@Nullable
private TaskFragment getLaunchTaskFragment() {
@@ -187,7 +193,7 @@
*/
boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType,
Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid,
- ActivityOptions activityOptions) {
+ ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) {
mUserManager = UserManager.get(mServiceContext);
mIntent = intent;
@@ -199,6 +205,7 @@
mInTask = inTask;
mInTaskFragment = inTaskFragment;
mActivityOptions = activityOptions;
+ mPresumableLaunchDisplayArea = presumableLaunchDisplayArea;
if (interceptQuietProfileIfNeeded()) {
// If work profile is turned off, skip the work challenge since the profile can only
@@ -221,6 +228,11 @@
if (interceptLockedManagedProfileIfNeeded()) {
return true;
}
+ if (interceptHomeIfNeeded()) {
+ // Replace primary home intents directed at displays that do not support primary home
+ // but support secondary home with the relevant secondary home activity.
+ return true;
+ }
final SparseArray<ActivityInterceptorCallback> callbacks =
mService.getActivityInterceptorCallbacks();
@@ -470,6 +482,47 @@
return true;
}
+ private boolean interceptHomeIfNeeded() {
+ if (mPresumableLaunchDisplayArea == null || mService.mRootWindowContainer == null) {
+ return false;
+ }
+ if (!ActivityRecord.isHomeIntent(mIntent)) {
+ return false;
+ }
+ if (!mIntent.hasCategory(Intent.CATEGORY_HOME)) {
+ // Already a secondary home intent, leave it alone.
+ return false;
+ }
+ if (mService.mRootWindowContainer.shouldPlacePrimaryHomeOnDisplay(
+ mPresumableLaunchDisplayArea.getDisplayId())) {
+ // Primary home can be launched to the display area.
+ return false;
+ }
+ if (!mService.mRootWindowContainer.shouldPlaceSecondaryHomeOnDisplayArea(
+ mPresumableLaunchDisplayArea)) {
+ // Secondary home cannot be launched on the display area.
+ return false;
+ }
+
+ // At this point we have a primary home intent for a display that does not support primary
+ // home activity but it supports secondary home one. So replace it with secondary home.
+ Pair<ActivityInfo, Intent> info = mService.mRootWindowContainer
+ .resolveSecondaryHomeActivity(mUserId, mPresumableLaunchDisplayArea);
+ mIntent = info.second;
+ // The new task flag is needed because the home activity should already be in the root task
+ // and should not be moved to the caller's task. Also, activities cannot change their type,
+ // e.g. a standard activity cannot become a home activity.
+ mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mCallingPid = mRealCallingPid;
+ mCallingUid = mRealCallingUid;
+ mResolvedType = null;
+
+ mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0,
+ mRealCallingUid, mRealCallingPid);
+ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ null);
+ return true;
+ }
+
private boolean isPackageSuspended() {
return mAInfo != null && mAInfo.applicationInfo != null
&& (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) != 0;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1bc78d6..458d1e8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1154,10 +1154,12 @@
}
}
+ final TaskDisplayArea suggestedLaunchDisplayArea =
+ computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions);
mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
callingFeatureId);
if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
- callingPid, callingUid, checkedOptions)) {
+ callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) {
// activity start was intercepted, e.g. because the target user is currently in quiet
// mode (turn off work) or the target application is suspended
intent = mInterceptor.mIntent;
@@ -1890,6 +1892,15 @@
mPreferredWindowingMode = mLaunchParams.mWindowingMode;
}
+ private TaskDisplayArea computeSuggestedLaunchDisplayArea(
+ Task task, ActivityRecord source, ActivityOptions options) {
+ mSupervisor.getLaunchParamsController().calculate(task, /*layout=*/null,
+ /*activity=*/ null, source, options, mRequest, PHASE_DISPLAY, mLaunchParams);
+ return mLaunchParams.hasPreferredTaskDisplayArea()
+ ? mLaunchParams.mPreferredTaskDisplayArea
+ : mRootWindowContainer.getDefaultTaskDisplayArea();
+ }
+
@VisibleForTesting
int isAllowedToStart(ActivityRecord r, boolean newTask, Task targetTask) {
if (r.packageName == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index bed52be..fd42077 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2648,9 +2648,15 @@
mAmInternal.enforceCallingPermission(Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
"updateLockTaskPackages()");
}
- synchronized (mGlobalLock) {
- ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowlisting %d:%s", userId, Arrays.toString(packages));
- getLockTaskController().updateLockTaskPackages(userId, packages);
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ ProtoLog.w(WM_DEBUG_LOCKTASK, "Allowlisting %d:%s", userId,
+ Arrays.toString(packages));
+ getLockTaskController().updateLockTaskPackages(userId, packages);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 9e3a611..97b3e32 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -880,6 +880,58 @@
false /* forTabletopMode */);
}
+ /**
+ * Overrides persistent horizontal position of the letterboxed app window when horizontal
+ * reachability is enabled.
+ */
+ void setPersistentLetterboxPositionForHorizontalReachability(boolean forBookMode,
+ @LetterboxHorizontalReachabilityPosition int position) {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ forBookMode, position);
+ }
+
+ /**
+ * Overrides persistent vertical position of the letterboxed app window when vertical
+ * reachability is enabled.
+ */
+ void setPersistentLetterboxPositionForVerticalReachability(boolean forTabletopMode,
+ @LetterboxVerticalReachabilityPosition int position) {
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ forTabletopMode, position);
+ }
+
+ /**
+ * Resets persistent horizontal position of the letterboxed app window when horizontal
+ * reachability
+ * is enabled to default position.
+ */
+ void resetPersistentLetterboxPositionForHorizontalReachability() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ false /* forBookMode */,
+ readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+ false /* forBookMode */));
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ true /* forBookMode */,
+ readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+ true /* forBookMode */));
+ }
+
+ /**
+ * Resets persistent vertical position of the letterboxed app window when vertical reachability
+ * is
+ * enabled to default position.
+ */
+ void resetPersistentLetterboxPositionForVerticalReachability() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ false /* forTabletopMode */,
+ readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+ false /* forTabletopMode */));
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ true /* forTabletopMode */,
+ readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+ true /* forTabletopMode */));
+ }
+
@LetterboxHorizontalReachabilityPosition
private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context,
boolean forBookMode) {
diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
index e646f14..106e142 100644
--- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
+++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Slog;
import android.view.IDisplayChangeWindowCallback;
import android.window.DisplayAreaInfo;
@@ -40,6 +41,7 @@
public class RemoteDisplayChangeController {
private static final String TAG = "RemoteDisplayChangeController";
+ private static final String REMOTE_DISPLAY_CHANGE_TRACE_TAG = "RemoteDisplayChange";
private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800;
@@ -82,6 +84,10 @@
}
mCallbacks.add(callback);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
+
if (newDisplayAreaInfo != null) {
ProtoLog.v(WM_DEBUG_CONFIGURATION,
"Starting remote display change: "
@@ -122,6 +128,10 @@
mCallbacks.clear();
}
callback.onContinueRemoteDisplayChange(null /* transaction */);
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
}
}
}
@@ -137,13 +147,23 @@
for (int i = 0; i < idx; ++i) {
// Expect remote callbacks in order. If they don't come in order, then force
// ordering by continuing everything up until this one with empty transactions.
- mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
+ ContinueRemoteDisplayChangeCallback currentCallback = mCallbacks.get(i);
+ currentCallback.onContinueRemoteDisplayChange(null /* transaction */);
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG,
+ currentCallback.hashCode());
+ }
}
mCallbacks.subList(0, idx + 1).clear();
if (mCallbacks.isEmpty()) {
mService.mH.removeCallbacks(mTimeoutRunnable);
}
callback.onContinueRemoteDisplayChange(transaction);
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 57f8268..d56acaa 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1608,6 +1608,19 @@
}
/**
+ * Check if the display is valid for primary home activity.
+ *
+ * @param displayId The target display ID
+ * @return {@code true} if allowed to launch, {@code false} otherwise.
+ */
+ boolean shouldPlacePrimaryHomeOnDisplay(int displayId) {
+ // No restrictions to default display, vr 2d display or main display for visible users.
+ return displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
+ && (displayId == mService.mVr2dDisplayId
+ || mWmService.shouldPlacePrimaryHomeOnDisplay(displayId)));
+ }
+
+ /**
* Check if the display area is valid for secondary home activity.
*
* @param taskDisplayArea The target display area.
@@ -1680,10 +1693,7 @@
final int displayId = taskDisplayArea != null ? taskDisplayArea.getDisplayId()
: INVALID_DISPLAY;
- if (displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY
- && (displayId == mService.mVr2dDisplayId
- || mWmService.shouldPlacePrimaryHomeOnDisplay(displayId)))) {
- // No restrictions to default display, vr 2d display or main display for visible users.
+ if (shouldPlacePrimaryHomeOnDisplay(displayId)) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 387a876..21a4fe8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2815,7 +2815,7 @@
final WindowManager.LayoutParams attrs = win.mAttrs;
visibleFrame.set(win.getFrame());
visibleFrame.inset(win.getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
- visibleFrame, attrs.type, win.getWindowingMode(), attrs.softInputMode,
+ visibleFrame, attrs.type, win.getActivityType(), attrs.softInputMode,
attrs.flags));
out.union(visibleFrame);
}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index ad46770..4e7a9bd 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -118,12 +118,14 @@
root = activity;
}
- if (root == null) {
+ if (root == null && phase != PHASE_DISPLAY) {
// There is a case that can lead us here. The caller is moving the top activity that is
// in a task that has multiple activities to PIP mode. For that the caller is creating a
// new task to host the activity so that we only move the top activity to PIP mode and
// keep other activities in the previous task. There is no point to apply the launch
// logic in this case.
+ // However, for PHASE_DISPLAY the root may be null, but we still want to get a hint of
+ // what the suggested launch display area would be.
return RESULT_SKIP;
}
@@ -395,8 +397,9 @@
}
private TaskDisplayArea getPreferredLaunchTaskDisplayArea(@Nullable Task task,
- @Nullable ActivityOptions options, ActivityRecord source, LaunchParams currentParams,
- @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+ @Nullable ActivityOptions options, @Nullable ActivityRecord source,
+ @Nullable LaunchParams currentParams, @Nullable ActivityRecord activityRecord,
+ @Nullable Request request) {
TaskDisplayArea taskDisplayArea = null;
final WindowContainerToken optionLaunchTaskDisplayAreaToken = options != null
@@ -438,8 +441,7 @@
// If the source activity is a no-display activity, pass on the launch display area token
// from source activity as currently preferred.
- if (taskDisplayArea == null && source != null
- && source.noDisplay) {
+ if (taskDisplayArea == null && source != null && source.noDisplay) {
taskDisplayArea = source.mHandoverTaskDisplayArea;
if (taskDisplayArea != null) {
if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
@@ -478,21 +480,24 @@
}
}
- if (taskDisplayArea == null) {
+ if (taskDisplayArea == null && currentParams != null) {
taskDisplayArea = currentParams.mPreferredTaskDisplayArea;
+ if (DEBUG) appendLog("display-area-from-current-params=" + taskDisplayArea);
}
// Re-route to default display if the device didn't declare support for multi-display
if (taskDisplayArea != null && !mSupervisor.mService.mSupportsMultiDisplay
&& taskDisplayArea.getDisplayId() != DEFAULT_DISPLAY) {
taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+ if (DEBUG) appendLog("display-area-from-no-multidisplay=" + taskDisplayArea);
}
// Re-route to default display if the home activity doesn't support multi-display
- if (taskDisplayArea != null && activityRecord.isActivityTypeHome()
+ if (taskDisplayArea != null && activityRecord != null && activityRecord.isActivityTypeHome()
&& !mSupervisor.mRootWindowContainer.canStartHomeOnDisplayArea(activityRecord.info,
taskDisplayArea, false /* allowInstrumenting */)) {
taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+ if (DEBUG) appendLog("display-area-from-home=" + taskDisplayArea);
}
return (taskDisplayArea != null)
@@ -516,34 +521,56 @@
* @return {@link TaskDisplayArea} to house the task
*/
private TaskDisplayArea getFallbackDisplayAreaForActivity(
- @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+ @Nullable ActivityRecord activityRecord, @Nullable Request request) {
+ if (activityRecord != null) {
+ WindowProcessController controllerFromLaunchingRecord =
+ mSupervisor.mService.getProcessController(
+ activityRecord.launchedFromPid, activityRecord.launchedFromUid);
+ if (controllerFromLaunchingRecord != null) {
+ final TaskDisplayArea taskDisplayAreaForLaunchingRecord =
+ controllerFromLaunchingRecord.getTopActivityDisplayArea();
+ if (taskDisplayAreaForLaunchingRecord != null) {
+ if (DEBUG) {
+ appendLog("display-area-for-launching-record="
+ + taskDisplayAreaForLaunchingRecord);
+ }
+ return taskDisplayAreaForLaunchingRecord;
+ }
+ }
- WindowProcessController controllerFromLaunchingRecord = mSupervisor.mService
- .getProcessController(activityRecord.launchedFromPid,
- activityRecord.launchedFromUid);
- final TaskDisplayArea displayAreaForLaunchingRecord = controllerFromLaunchingRecord == null
- ? null : controllerFromLaunchingRecord.getTopActivityDisplayArea();
- if (displayAreaForLaunchingRecord != null) {
- return displayAreaForLaunchingRecord;
+ WindowProcessController controllerFromProcess =
+ mSupervisor.mService.getProcessController(
+ activityRecord.getProcessName(), activityRecord.getUid());
+ if (controllerFromProcess != null) {
+ final TaskDisplayArea displayAreaForRecord =
+ controllerFromProcess.getTopActivityDisplayArea();
+ if (displayAreaForRecord != null) {
+ if (DEBUG) appendLog("display-area-for-record=" + displayAreaForRecord);
+ return displayAreaForRecord;
+ }
+ }
}
- WindowProcessController controllerFromProcess = mSupervisor.mService.getProcessController(
- activityRecord.getProcessName(), activityRecord.getUid());
- final TaskDisplayArea displayAreaForRecord = controllerFromProcess == null ? null
- : controllerFromProcess.getTopActivityDisplayArea();
- if (displayAreaForRecord != null) {
- return displayAreaForRecord;
+ if (request != null) {
+ WindowProcessController controllerFromRequest =
+ mSupervisor.mService.getProcessController(
+ request.realCallingPid, request.realCallingUid);
+ if (controllerFromRequest != null) {
+ final TaskDisplayArea displayAreaFromSourceProcess =
+ controllerFromRequest.getTopActivityDisplayArea();
+ if (displayAreaFromSourceProcess != null) {
+ if (DEBUG) {
+ appendLog("display-area-source-process=" + displayAreaFromSourceProcess);
+ }
+ return displayAreaFromSourceProcess;
+ }
+ }
}
- WindowProcessController controllerFromRequest = request == null ? null : mSupervisor
- .mService.getProcessController(request.realCallingPid, request.realCallingUid);
- final TaskDisplayArea displayAreaFromSourceProcess = controllerFromRequest == null ? null
- : controllerFromRequest.getTopActivityDisplayArea();
- if (displayAreaFromSourceProcess != null) {
- return displayAreaFromSourceProcess;
- }
-
- return mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+ final TaskDisplayArea defaultTaskDisplayArea =
+ mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+ if (DEBUG) appendLog("display-area-from-default-fallback=" + defaultTaskDisplayArea);
+ return defaultTaskDisplayArea;
}
private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display,
@@ -559,7 +586,7 @@
return false;
}
- final int sourceWindowingMode = source.getWindowingMode();
+ final int sourceWindowingMode = source.getTask().getWindowingMode();
if (sourceWindowingMode != WINDOWING_MODE_FULLSCREEN
&& sourceWindowingMode != WINDOWING_MODE_FREEFORM) {
return false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index ceebb27..bfe0553 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -911,6 +911,70 @@
return 0;
}
+ private int runSetPersistentLetterboxPositionForHorizontalReachability(PrintWriter pw)
+ throws RemoteException {
+ @LetterboxHorizontalReachabilityPosition final int position;
+ try {
+ String arg = getNextArgRequired();
+ switch (arg) {
+ case "left":
+ position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+ break;
+ case "center":
+ position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+ break;
+ case "right":
+ position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument" + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
+ false /* IsInBookMode */, position);
+ }
+ return 0;
+ }
+
+ private int runSetPersistentLetterboxPositionForVerticalReachability(PrintWriter pw)
+ throws RemoteException {
+ @LetterboxVerticalReachabilityPosition final int position;
+ try {
+ String arg = getNextArgRequired();
+ switch (arg) {
+ case "top":
+ position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+ break;
+ case "center":
+ position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+ break;
+ case "bottom":
+ position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'top', 'center' or 'bottom' are expected as an argument");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'top', 'center' or 'bottom' are expected as an argument" + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability(
+ false /* forTabletopMode */, position);
+ }
+ return 0;
+ }
+
private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter)
throws RemoteException {
String arg = getNextArg();
@@ -994,6 +1058,12 @@
case "--defaultPositionForVerticalReachability":
runSetLetterboxDefaultPositionForVerticalReachability(pw);
break;
+ case "--persistentPositionForHorizontalReachability":
+ runSetPersistentLetterboxPositionForHorizontalReachability(pw);
+ break;
+ case "--persistentPositionForVerticalReachability":
+ runSetPersistentLetterboxPositionForVerticalReachability(pw);
+ break;
case "--isEducationEnabled":
runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled);
break;
@@ -1080,6 +1150,14 @@
case "defaultPositionForVerticalReachability":
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
break;
+ case "persistentPositionForHorizontalReachability":
+ mLetterboxConfiguration
+ .resetPersistentLetterboxPositionForHorizontalReachability();
+ break;
+ case "persistentPositionForVerticalReachability":
+ mLetterboxConfiguration
+ .resetPersistentLetterboxPositionForVerticalReachability();
+ break;
case "isEducationEnabled":
mLetterboxConfiguration.resetIsEducationEnabled();
break;
@@ -1206,6 +1284,8 @@
mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
+ mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability();
+ mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability();
mLetterboxConfiguration.resetIsEducationEnabled();
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
@@ -1233,6 +1313,12 @@
pw.println("Vertical position multiplier (tabletop mode): "
+ mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(
true /* isInTabletopMode */));
+ pw.println("Horizontal position multiplier for reachability: "
+ + mLetterboxConfiguration.getHorizontalMultiplierForReachability(
+ false /* isInBookMode */));
+ pw.println("Vertical position multiplier for reachability: "
+ + mLetterboxConfiguration.getVerticalMultiplierForReachability(
+ false /* isInTabletopMode */));
pw.println("Aspect ratio: "
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println("Default min aspect ratio for unresizable apps: "
@@ -1472,6 +1558,12 @@
pw.println(" --defaultPositionForVerticalReachability [top|center|bottom]");
pw.println(" Default position of app window when vertical reachability is.");
pw.println(" enabled.");
+ pw.println(" --persistentPositionForHorizontalReachability [left|center|right]");
+ pw.println(" Persistent position of app window when horizontal reachability is.");
+ pw.println(" enabled.");
+ pw.println(" --persistentPositionForVerticalReachability [top|center|bottom]");
+ pw.println(" Persistent position of app window when vertical reachability is.");
+ pw.println(" enabled.");
pw.println(" --isEducationEnabled [true|1|false|0]");
pw.println(" Whether education is allowed for letterboxed fullscreen apps.");
pw.println(" --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
@@ -1493,8 +1585,10 @@
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
pw.println(" |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
- pw.println(" |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+ pw.println(" |isEducationEnabled|defaultPositionMultiplierForHorizontalReachability");
pw.println(" |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled");
+ pw.println(" |persistentPositionMultiplierForHorizontalReachability");
+ pw.println(" |persistentPositionMultiplierForVerticalReachability");
pw.println(" |defaultPositionMultiplierForVerticalReachability]");
pw.println(" Resets overrides to default values for specified properties separated");
pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a172d99..9c04e0a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1759,7 +1759,7 @@
bounds.set(mWindowFrames.mFrame);
bounds.inset(getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
- bounds, mAttrs.type, getWindowingMode(), mAttrs.softInputMode, mAttrs.flags));
+ bounds, mAttrs.type, getActivityType(), mAttrs.softInputMode, mAttrs.flags));
if (intersectWithRootTaskBounds) {
bounds.intersect(mTmpRect);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2be2bb9..438a9d6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -680,7 +680,7 @@
// to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to
// be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade
// step.
- static final int DPMS_VERSION = 5;
+ static final int DPMS_VERSION = 6;
static {
SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
@@ -876,8 +876,7 @@
private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
// TODO(b/265683382) remove the flag after rollout.
- private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
- public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
+ public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
// TODO(b/261999445) remove the flag after rollout.
private static final String HEADLESS_FLAG = "headless";
@@ -23011,10 +23010,7 @@
}
private static boolean isKeepProfilesRunningFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- KEEP_PROFILES_RUNNING_FLAG,
- DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+ return DEFAULT_KEEP_PROFILES_RUNNING_FLAG;
}
private boolean isUnicornFlagEnabled() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
index 733b1d9..f060426 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyVersionUpgrader.java
@@ -117,6 +117,19 @@
currentVersion = 5;
}
+ if (currentVersion == 5) {
+ Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
+ // No-op upgrade here:
+ // DevicePolicyData.mEffectiveKeepProfilesRunning is only stored in XML file when it is
+ // different from its default value, otherwise the tag is not written. When loading, if
+ // the tag is missing, the field retains the value previously assigned in the
+ // constructor, which is the default value.
+ // In version 5 the default value was 'true', in version 6 it is 'false', so when
+ // loading XML version 5 we need to initialize the field to 'true' for it to be restored
+ // correctly in case the tag is missing. This is done in loadDataForUser().
+ currentVersion = 6;
+ }
+
writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion);
}
@@ -282,6 +295,10 @@
private DevicePolicyData loadDataForUser(
int userId, int loadVersion, ComponentName ownerComponent) {
DevicePolicyData policy = new DevicePolicyData(userId);
+ // See version 5 -> 6 step in upgradePolicy()
+ if (loadVersion == 5 && userId == UserHandle.USER_SYSTEM) {
+ policy.mEffectiveKeepProfilesRunning = true;
+ }
DevicePolicyData.load(policy,
mProvider.makeDevicePoliciesJournaledFile(userId),
mProvider.getAdminInfoSupplier(userId),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d099693..40d7a77 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -28,6 +28,7 @@
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -53,6 +54,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PropertyInvalidatedCache;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceManager;
@@ -627,7 +630,7 @@
* Tests that the virtual display is created along-side the default display.
*/
@Test
- public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception {
+ public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mShortMockedInjector);
registerDefaultDisplays(displayManager);
@@ -663,7 +666,7 @@
* internal state for things like display cutout when nonOverrideDisplayInfo is changed.
*/
@Test
- public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exception {
+ public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mShortMockedInjector);
registerDefaultDisplays(displayManager);
@@ -1486,7 +1489,7 @@
* a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
*/
@Test
- public void testOwnDisplayGroup_allowCreationWithVirtualDevice() throws Exception {
+ public void testOwnDisplayGroup_allowCreationWithVirtualDevice() throws Exception {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -1650,7 +1653,7 @@
*/
@Test
@DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
- public void testDisplayInfoFrameRateOverrideModeCompat() throws Exception {
+ public void testDisplayInfoFrameRateOverrideModeCompat() {
testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ false);
}
@@ -1659,7 +1662,7 @@
*/
@Test
@EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
- public void testDisplayInfoFrameRateOverrideMode() throws Exception {
+ public void testDisplayInfoFrameRateOverrideMode() {
testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ true);
}
@@ -1742,7 +1745,7 @@
*/
@Test
@DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
- public void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+ public void testDisplayInfoRenderFrameRateModeCompat() {
testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
}
@@ -1751,7 +1754,7 @@
*/
@Test
@EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
- public void testDisplayInfoRenderFrameRateMode() throws Exception {
+ public void testDisplayInfoRenderFrameRateMode() {
testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
}
@@ -2104,10 +2107,11 @@
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
@@ -2124,16 +2128,19 @@
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
- // Create default display device
- createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-
bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
localService.registerDisplayGroupListener(callback);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
+ // Create default display device
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ callback.waitForExpectedEvent();
+ callback.clear();
+ callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
@@ -2151,8 +2158,9 @@
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CONNECTED,
EVENT_DISPLAY_ADDED).inOrder();
@@ -2166,18 +2174,22 @@
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
// Create default display device
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
- bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+ callback.waitForExpectedEvent();
+ callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
displayManager.enableConnectedDisplay(display.getDisplayIdLocked(), /* enabled= */ true);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(display.isEnabledLocked()).isTrue();
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED).inOrder();
@@ -2190,11 +2202,15 @@
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
// Create default display device
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ callback.waitForExpectedEvent();
bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+ // Withouts permission, we cannot get the CONNECTED event.
waitForIdleHandler(displayManager.getDisplayHandler());
callback.clear();
LogicalDisplay display =
@@ -2212,19 +2228,22 @@
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+ callback.expectsEvent(EVENT_DISPLAY_REMOVED);
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
logicalDisplayMapper.updateLogicalDisplays();
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
logicalDisplayMapper.updateLogicalDisplays();
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED);
}
@@ -2237,16 +2256,18 @@
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+ callback.expectsEvent(EVENT_DISPLAY_REMOVED);
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
logicalDisplayMapper.updateLogicalDisplays();
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED);
}
@@ -2259,23 +2280,26 @@
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
DisplayManagerInternal localService = displayManager.new LocalService();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
- // Create default display device
- createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
localService.registerDisplayGroupListener(callback);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
+ // Create default display device
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ callback.waitForExpectedEvent();
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
logicalDisplayMapper.updateLogicalDisplays();
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
+ callback.expectsEvent(EVENT_DISPLAY_REMOVED);
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
logicalDisplayMapper.updateLogicalDisplays();
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(display.isEnabledLocked()).isFalse();
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED);
@@ -2288,18 +2312,20 @@
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
// Create default display device
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
- bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+ callback.waitForExpectedEvent();
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
int displayId = display.getDisplayIdLocked();
logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
logicalDisplayMapper.updateLogicalDisplays();
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
assertThrows(SecurityException.class, () -> bs.disableConnectedDisplay(displayId));
@@ -2314,23 +2340,27 @@
DisplayManagerInternal localService = displayManager.new LocalService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
- // Create default display device
- createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
localService.registerDisplayGroupListener(callback);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
+ // Create default display device'
+ createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+ callback.waitForExpectedEvent();
+ callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
LogicalDisplay display = logicalDisplayMapper.getDisplayLocked(displayDevice);
int groupId = display.getDisplayInfoLocked().displayGroupId;
DisplayGroup group = logicalDisplayMapper.getDisplayGroupLocked(groupId);
assertThat(group.getSizeLocked()).isEqualTo(1);
+ callback.expectsEvent(DISPLAY_GROUP_EVENT_REMOVED);
display.setPrimaryDisplayDeviceLocked(null);
displayManager.getDisplayDeviceRepository()
.onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(group.getSizeLocked()).isEqualTo(0);
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_DISCONNECTED,
@@ -2357,20 +2387,23 @@
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_CONNECTED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
int displayId = display.getDisplayIdLocked();
displayManager.enableConnectedDisplay(displayId, /* enabled= */ true);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
+ callback.expectsEvent(EVENT_DISPLAY_DISCONNECTED);
display.setPrimaryDisplayDeviceLocked(null);
displayManager.getDisplayDeviceRepository()
.onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(logicalDisplayMapper.getDisplayLocked(displayId, true)).isNull();
assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED,
@@ -2386,17 +2419,19 @@
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+ callback.expectsEvent(EVENT_DISPLAY_ADDED);
FakeDisplayDevice displayDevice =
createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
callback.clear();
+ callback.expectsEvent(EVENT_DISPLAY_DISCONNECTED);
display.setPrimaryDisplayDeviceLocked(null);
displayManager.getDisplayDeviceRepository()
.onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
- waitForIdleHandler(displayManager.getDisplayHandler());
+ callback.waitForExpectedEvent();
assertThat(logicalDisplayMapper.getDisplayLocked(displayDevice,
/* includeDisabled= */ true)).isNull();
@@ -2675,6 +2710,12 @@
int mDisplayId;
List<String> mReceivedEvents = new ArrayList<>();
+ @Nullable
+ private String mExpectedEvent;
+
+ @NonNull
+ private volatile CountDownLatch mLatch = new CountDownLatch(0);
+
FakeDisplayManagerCallback(int displayId) {
mDisplayId = displayId;
}
@@ -2683,6 +2724,30 @@
mDisplayId = -1;
}
+ void expectsEvent(@NonNull String event) {
+ mExpectedEvent = event;
+ mLatch = new CountDownLatch(1);
+ }
+
+ void waitForExpectedEvent() {
+ waitForExpectedEvent(Duration.ofSeconds(1));
+ }
+
+ void waitForExpectedEvent(Duration timeout) {
+ try {
+ assertWithMessage("Event '" + mExpectedEvent + "' is received.")
+ .that(mLatch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException ex) {
+ throw new AssertionError("Waiting for expected event interrupted", ex);
+ }
+ }
+
+ private void eventSeen(String event) {
+ if (event.equals(mExpectedEvent)) {
+ mLatch.countDown();
+ }
+ }
+
@Override
public void onDisplayEvent(int displayId, int event) {
if (mDisplayId != -1 && displayId != mDisplayId) {
@@ -2693,22 +2758,27 @@
// 1 - The error produced is a lot easier to read
// 2 - The values used for display and group events are the same, strings are used to
// differentiate them easily.
- mReceivedEvents.add(eventTypeToString(event));
+ String eventName = eventTypeToString(event);
+ mReceivedEvents.add(eventName);
+ eventSeen(eventName);
}
@Override
public void onDisplayGroupAdded(int groupId) {
mReceivedEvents.add(DISPLAY_GROUP_EVENT_ADDED);
+ eventSeen(DISPLAY_GROUP_EVENT_ADDED);
}
@Override
public void onDisplayGroupRemoved(int groupId) {
mReceivedEvents.add(DISPLAY_GROUP_EVENT_REMOVED);
+ eventSeen(DISPLAY_GROUP_EVENT_REMOVED);
}
@Override
public void onDisplayGroupChanged(int groupId) {
mReceivedEvents.add(DISPLAY_GROUP_EVENT_CHANGED);
+ eventSeen(DISPLAY_GROUP_EVENT_CHANGED);
}
public void clear() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 0195ff7..e7dc48e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -1160,6 +1160,21 @@
eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE));
}
+ @Test
+ public void testPowerStateStopsOnDpcStop() {
+ // Set up
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1);
+
+ // Stop dpc
+ mHolder.dpc.stop();
+ advanceTime(1);
+
+ // Ensure dps has stopped
+ verify(mHolder.displayPowerState, times(1)).stop();
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index f499ac5..2640390 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1167,6 +1167,21 @@
eq(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE_IDLE));
}
+ @Test
+ public void testPowerStateStopsOnDpcStop() {
+ // Set up
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1);
+
+ // Stop dpc
+ mHolder.dpc.stop();
+ advanceTime(1);
+
+ // Ensure dps has stopped
+ verify(mHolder.displayPowerState, times(1)).stop();
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java
new file mode 100644
index 0000000..167a412
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.times;
+
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+
+@SmallTest
+public class DisplayPowerStateTest {
+ private static final int DISPLAY_ID = 123;
+
+ private DisplayPowerState mDisplayPowerState;
+ private TestLooper mTestLooper;
+ @Mock
+ private DisplayBlanker mDisplayBlankerMock;
+ @Mock
+ private ColorFade mColorFadeMock;
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Before
+ public void setUp() {
+ mTestLooper = new TestLooper();
+ mDisplayPowerState = new DisplayPowerState(
+ mDisplayBlankerMock, mColorFadeMock, DISPLAY_ID, Display.STATE_ON,
+ new Handler(mTestLooper.getLooper()));
+ }
+
+ @Test
+ public void testColorFadeStopsOnDpsStop() {
+ mDisplayPowerState.stop();
+ verify(mColorFadeMock, times(1)).stop();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
index c7e1bda..e58a234 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
@@ -16,41 +16,51 @@
package com.android.server.pm;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
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.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageArchiver;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.ParcelableException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateImpl;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,7 +74,8 @@
public class PackageArchiverServiceTest {
private static final String PACKAGE = "com.example";
- private static final String CALLER_PACKAGE = "com.vending";
+ private static final String CALLER_PACKAGE = "com.caller";
+ private static final String INSTALLER_PACKAGE = "com.installer";
@Rule
public final MockSystemRule mMockSystem = new MockSystemRule();
@@ -84,11 +95,11 @@
private final InstallSource mInstallSource =
InstallSource.create(
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- CALLER_PACKAGE,
+ INSTALLER_PACKAGE,
+ INSTALLER_PACKAGE,
+ INSTALLER_PACKAGE,
Binder.getCallingUid(),
- CALLER_PACKAGE,
+ INSTALLER_PACKAGE,
/* installerAttributionTag= */ null,
/* packageSource= */ 0);
@@ -96,6 +107,8 @@
private final int mUserId = UserHandle.CURRENT.getIdentifier();
+ private PackageUserStateImpl mUserState;
+
private PackageSetting mPackageSetting;
private PackageArchiverService mArchiveService;
@@ -116,11 +129,16 @@
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
mPackageState);
+ when(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(),
+ anyInt())).thenReturn(mock(PackageStateInternal.class));
when(mPackageState.getPackageName()).thenReturn(PACKAGE);
when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
mPackageSetting = createBasicPackageSetting();
when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
mPackageSetting);
+ mUserState = new PackageUserStateImpl().setInstalled(true);
+ mPackageSetting.setUserState(mUserId, mUserState);
+ when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
@@ -135,9 +153,7 @@
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender,
- UserHandle.CURRENT
- )
- );
+ UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
@@ -154,9 +170,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT
- )
- );
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
@@ -169,9 +183,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT
- )
- );
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
@@ -193,25 +205,28 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT
- )
- );
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("No installer found to archive app %s.", PACKAGE));
}
@Test
- public void archiveApp_success() {
- List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
- for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
- // TODO(b/278553670) Extract and store launcher icons
- ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
- mainActivity.getLabel().toString(),
- Path.of("/TODO"), null);
- activityInfos.add(activityInfo);
- }
+ public void archiveApp_noMainActivities() {
+ when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
+ List.of());
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_success() {
mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
verify(mInstallerService).uninstall(
@@ -220,7 +235,112 @@
eq(UserHandle.CURRENT.getIdentifier()));
assertThat(mPackageSetting.readUserState(
UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
- new ArchiveState(activityInfos, CALLER_PACKAGE));
+ createArchiveState());
+ }
+
+ @Test
+ public void unarchiveApp_callerPackageNameIncorrect() {
+ mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+ Exception e = assertThrows(
+ SecurityException.class,
+ () -> mArchiveService.requestUnarchive(PACKAGE, "different",
+ UserHandle.CURRENT));
+ assertThat(e).hasMessageThat().isEqualTo(
+ String.format(
+ "The UID %s of callerPackageName set by the caller doesn't match the "
+ + "caller's actual UID %s.",
+ 0,
+ Binder.getCallingUid()));
+ }
+
+ @Test
+ public void unarchiveApp_packageNotInstalled() {
+ mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+ when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+ null);
+
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+ UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s not found.", PACKAGE));
+ }
+
+ @Test
+ public void unarchiveApp_notArchived() {
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+ UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s is not currently archived.", PACKAGE));
+ }
+
+ @Test
+ public void unarchiveApp_noInstallerFound() {
+ mUserState.setArchiveState(createArchiveState());
+ InstallSource otherInstallSource =
+ InstallSource.create(
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ /* installerPackageName= */ null,
+ Binder.getCallingUid(),
+ /* updateOwnerPackageName= */ null,
+ /* installerAttributionTag= */ null,
+ /* packageSource= */ 0);
+ when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+ UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("No installer found to unarchive app %s.", PACKAGE));
+ }
+
+ @Test
+ public void unarchiveApp_success() {
+ mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+ mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+ mMockSystem.mocks().getHandler().flush();
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendOrderedBroadcastAsUser(
+ intentCaptor.capture(),
+ eq(UserHandle.CURRENT),
+ /* receiverPermission = */ isNull(),
+ eq(AppOpsManager.OP_NONE),
+ any(Bundle.class),
+ /* resultReceiver= */ isNull(),
+ /* scheduler= */ isNull(),
+ /* initialCode= */ eq(0),
+ /* initialData= */ isNull(),
+ /* initialExtras= */ isNull());
+ Intent intent = intentCaptor.getValue();
+ assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0);
+ assertThat(intent.getStringExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
+ PACKAGE);
+ assertThat(
+ intent.getBooleanExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
+ assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
+ }
+
+ private static ArchiveState createArchiveState() {
+ List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+ for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
+ // TODO(b/278553670) Extract and store launcher icons
+ ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
+ mainActivity.getLabel().toString(),
+ Path.of("/TODO"), null);
+ activityInfos.add(activityInfo);
+ }
+ return new ArchiveState(activityInfos, INSTALLER_PACKAGE);
}
private static List<LauncherActivityInfo> createLauncherActivities() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 534aa89..93cbea6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -243,8 +243,10 @@
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
final boolean includeProcessStateData = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
+ final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customPowerComponentNames, includePowerModels, includeProcessStateData);
+ customPowerComponentNames, includePowerModels, includeProcessStateData,
+ minConsumedPowerThreshold);
SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
for (int i = 0; i < uidStats.size(); i++) {
builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 266a226..07c486c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -181,7 +181,7 @@
final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
final BatteryUsageStats sum =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
.add(stats1)
.add(stats2)
.build();
@@ -222,7 +222,7 @@
@Test
public void testAdd_customComponentMismatch() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -231,7 +231,7 @@
@Test
public void testAdd_processStateDataMismatch() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -260,7 +260,7 @@
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
@@ -305,7 +305,7 @@
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(customPowerComponentNames, true,
- includeProcessStateData);
+ includeProcessStateData, 0);
builder.setDischargePercentage(30)
.setDischargedPowerRange(1234, 2345)
.setStatsStartTimestamp(2000)
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
new file mode 100644
index 0000000..4785a88
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_false.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <keep-profiles-running value="false" />
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991" />
+ <strong-auth-unlock-timeout value="0" />
+ <organization-color value="-16738680" />
+ <active-password value="0" />
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
new file mode 100644
index 0000000..07ec229
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/device_policies_keep_profiles_running_true.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <keep-profiles-running value="true" />
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991" />
+ <strong-auth-unlock-timeout value="0" />
+ <organization-color value="-16738680" />
+ <active-password value="0" />
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
index eb2ee35..d2b921d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PolicyVersionUpgraderTest.java
@@ -76,7 +76,7 @@
public class PolicyVersionUpgraderTest extends DpmTestBase {
// NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade
// to the new version.
- private static final int LATEST_TESTED_VERSION = 5;
+ private static final int LATEST_TESTED_VERSION = 6;
public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions";
public static final String DEVICE_OWNER_XML = "device_owner_2.xml";
private ComponentName mFakeAdmin;
@@ -313,7 +313,7 @@
}
@Test
- public void testEffectiveKeepProfilesRunningSet() throws Exception {
+ public void testEffectiveKeepProfilesRunningSetToFalse4To5() throws Exception {
writeVersionToXml(4);
final int userId = UserHandle.USER_SYSTEM;
@@ -327,8 +327,109 @@
Document policies = readPolicies(userId);
Element keepProfilesRunning = (Element) policies.getDocumentElement()
.getElementsByTagName("keep-profiles-running").item(0);
- assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("false");
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
}
+ @Test
+ public void testEffectiveKeepProfilesRunningIsToFalse4To6() throws Exception {
+ writeVersionToXml(4);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
+ }
+
+ /**
+ * Verify correct behaviour when upgrading from Android 13
+ */
+ @Test
+ public void testEffectiveKeepProfilesRunningIsToFalse3To6() throws Exception {
+ writeVersionToXml(3);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
+ }
+
+ @Test
+ public void testEffectiveKeepProfilesRunningMissingInV5() throws Exception {
+ writeVersionToXml(5);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+ assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+ }
+
+ @Test
+ public void testEffectiveKeepProfilesRunningTrueInV5() throws Exception {
+ writeVersionToXml(5);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies_keep_profiles_running_true.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+ assertThat(keepProfilesRunning.getAttribute("value")).isEqualTo("true");
+ }
+
+ @Test
+ public void testEffectiveKeepProfilesRunningFalseInV5() throws Exception {
+ writeVersionToXml(5);
+
+ final int userId = UserHandle.USER_SYSTEM;
+ mProvider.mUsers = new int[]{userId};
+ preparePoliciesFile(userId, "device_policies_keep_profiles_running_false.xml");
+
+ mUpgrader.upgradePolicy(6);
+
+ assertThat(readVersionFromXml()).isAtLeast(6);
+
+ Document policies = readPolicies(userId);
+ Element keepProfilesRunning = (Element) policies.getDocumentElement()
+ .getElementsByTagName("keep-profiles-running").item(0);
+
+ // Default value (false) is not serialized.
+ assertThat(keepProfilesRunning).isNull();
+ }
+
@Test
public void isLatestVersionTested() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index 10b49c6..bc826a3 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -30,7 +30,6 @@
import android.content.res.Resources;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
import android.util.AtomicFile;
import android.util.SparseArray;
@@ -73,12 +72,10 @@
VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1);
@Mock private Resources mResourcesMock;
- @Mock private Vibrator mVibratorMock;
@Mock private VibratorInfo mVibratorInfoMock;
@Before
public void setUp() {
- when(mVibratorMock.getInfo()).thenReturn(mVibratorInfoMock);
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
}
@@ -220,17 +217,17 @@
public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
setCustomizationFilePath("");
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
.isNull();
setCustomizationFilePath(null);
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
.isNull();
setCustomizationFilePath("non_existent_file.xml");
- assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
.isNull();
}
@@ -387,7 +384,7 @@
String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
setupCustomizationFile(xml);
assertThat(expectedCustomizations.contentEquals(
- HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)))
+ HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
.isTrue();
}
@@ -395,13 +392,15 @@
setupCustomizationFile(xml);
assertThrows("Expected haptic feedback customization to fail for " + xml,
CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
+ () -> HapticFeedbackCustomization.loadVibrations(
+ mResourcesMock, mVibratorInfoMock));
}
private void assertParseCustomizationsFails() throws Exception {
assertThrows("Expected haptic feedback customization to fail",
CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
+ () -> HapticFeedbackCustomization.loadVibrations(
+ mResourcesMock, mVibratorInfoMock));
}
private void setupCustomizationFile(String xml) throws Exception {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index cae811e..a91bd2b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -31,8 +31,10 @@
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
-import android.os.test.FakeVibrator;
+import android.os.VibratorInfo;
import android.util.AtomicFile;
import android.util.SparseArray;
@@ -58,7 +60,7 @@
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
private Context mContext = InstrumentationRegistry.getContext();
- private FakeVibrator mVibrator = new FakeVibrator(mContext);
+ private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
@Mock private Resources mResourcesMock;
@@ -66,14 +68,14 @@
public void testNonExistentCustomization_useDefault() throws Exception {
// No customization file is set.
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
// The customization file specifies no customization.
setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
- hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+ hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -83,7 +85,7 @@
public void testExceptionParsingCustomizations_useDefault() throws Exception {
setupCustomizationFile("<bad-xml></bad-xml>");
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -96,7 +98,7 @@
customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
// The override for `CONTEXT_CLICK` is used.
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -109,11 +111,15 @@
@Test
public void testDoNotUseInvalidCustomizedVibration() throws Exception {
mockVibratorPrimitiveSupport(new int[] {});
- SparseArray<VibrationEffect> customizations = new SparseArray<>();
- customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"" + CONTEXT_CLICK + "\">"
+ + PRIMITIVE_CLICK_EFFECT
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ setupCustomizationFile(xml);
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
// The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -132,14 +138,14 @@
// Test with a customization available for `TEXT_HANDLE_MOVE`.
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
// Test with no customization available for `TEXT_HANDLE_MOVE`.
hapticProvider =
new HapticFeedbackVibrationProvider(
- mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+ mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
}
@@ -153,7 +159,7 @@
// Test with a customization available for `TEXT_HANDLE_MOVE`.
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
@@ -161,7 +167,7 @@
// Test with no customization available for `TEXT_HANDLE_MOVE`.
hapticProvider =
new HapticFeedbackVibrationProvider(
- mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+ mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
@@ -176,14 +182,14 @@
customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
mockSafeModeEnabledVibration(null);
hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
@@ -193,20 +199,9 @@
public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
throws Exception {
mockSafeModeEnabledVibration(10, 20, 30, 40);
- SparseArray<VibrationEffect> customizations = new SparseArray<>();
- customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
-
- // Test with a customization that is not supported by the vibrator.
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
- .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
-
- // Test with no customizations.
- hapticProvider =
new HapticFeedbackVibrationProvider(
- mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+ mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
.isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
@@ -216,25 +211,44 @@
public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
throws Exception {
mockSafeModeEnabledVibration(null);
- SparseArray<VibrationEffect> customizations = new SparseArray<>();
- customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
-
- // Test with a customization that is not supported by the vibrator.
HapticFeedbackVibrationProvider hapticProvider =
- new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
-
- // Test with no customizations.
- hapticProvider =
new HapticFeedbackVibrationProvider(
- mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+ mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
}
+ @Test
+ public void testVibrationAttribute_forNotBypassingIntensitySettings() {
+ HapticFeedbackVibrationProvider hapticProvider =
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
+
+ VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false);
+
+ assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void testVibrationAttribute_forByassingIntensitySettings() {
+ HapticFeedbackVibrationProvider hapticProvider =
+ new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
+
+ VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+ SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true);
+
+ assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+ .isNotEqualTo(0);
+ }
+
private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
- mVibrator = new FakeVibrator(mContext, supportedPrimitives);
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ for (int primitive : supportedPrimitives) {
+ builder.setSupportedPrimitive(primitive, 10);
+ }
+ mVibratorInfo = builder.build();
}
private void mockHapticTextSupport(boolean supported) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4e3a893..c25f0cb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,6 +31,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -45,6 +47,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManagerInternal;
+import android.content.res.Resources;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
@@ -79,8 +82,10 @@
import android.os.vibrator.VibrationEffectSegment;
import android.provider.Settings;
import android.util.ArraySet;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
+import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
@@ -169,6 +174,8 @@
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
+ private SparseArray<VibrationEffect> mHapticFeedbackVibrationMap = new SparseArray<>();
+
private VibratorManagerService mService;
private Context mContextSpy;
private TestLooper mTestLooper;
@@ -309,6 +316,12 @@
mExternalVibratorService =
(VibratorManagerService.ExternalVibratorService) serviceInstance;
}
+
+ HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
+ Resources resources, VibratorInfo vibratorInfo) {
+ return new HapticFeedbackVibrationProvider(
+ resources, vibratorInfo, mHapticFeedbackVibrationMap);
+ }
});
return mService;
}
@@ -623,6 +636,18 @@
}
@Test
+ public void vibrate_withoutVibratePermission_throwsSecurityException() {
+ denyPermission(android.Manifest.permission.VIBRATE);
+ VibratorManagerService service = createSystemReadyService();
+
+ assertThrows("Expected vibrating without permission to fail!",
+ SecurityException.class,
+ () -> vibrate(service,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH)));
+ }
+
+ @Test
public void vibrate_withRingtone_usesRingerModeSettings() throws Exception {
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
@@ -1274,6 +1299,60 @@
}
@Test
+ public void performHapticFeedback_doesNotRequirePermission() throws Exception {
+ denyPermission(android.Manifest.permission.VIBRATE);
+ mHapticFeedbackVibrationMap.put(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ HalVibration vibration =
+ performHapticFeedbackAndWaitUntilFinished(
+ service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+
+ List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+ assertEquals(1, playedSegments.size());
+ PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
+ assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
+ assertEquals(VibrationAttributes.USAGE_TOUCH, vibration.callerInfo.attrs.getUsage());
+ }
+
+ @Test
+ public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
+ denyPermission(android.Manifest.permission.VIBRATE);
+ mHapticFeedbackVibrationMap.put(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setVibratorInfoLoadSuccessful(false);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createService();
+
+ performHapticFeedbackAndWaitUntilFinished(
+ service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+
+ assertTrue(fakeVibrator.getAllEffectSegments().isEmpty());
+ }
+
+ @Test
+ public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception {
+ denyPermission(android.Manifest.permission.VIBRATE);
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ // These are bad haptic feedback IDs, so expect no vibration played.
+ performHapticFeedbackAndWaitUntilFinished(service, /* constant= */ -1, /* always= */ false);
+ performHapticFeedbackAndWaitUntilFinished(
+ service, HapticFeedbackConstants.NO_HAPTICS, /* always= */ true);
+
+ assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
+ }
+
+ @Test
public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
int defaultNotificationIntensity =
mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
@@ -2231,6 +2310,18 @@
mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
}
+ private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
+ int constant, boolean always) throws InterruptedException {
+ HalVibration vib =
+ service.performHapticFeedbackInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+ constant, always, "some reason", service);
+ if (vib != null) {
+ vib.waitForEnd();
+ }
+
+ return vib;
+ }
+
private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
VibrationAttributes attrs) throws InterruptedException {
vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
@@ -2239,8 +2330,8 @@
private void vibrateAndWaitUntilFinished(VibratorManagerService service,
CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
HalVibration vib =
- service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
- "some reason", service);
+ service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+ effect, attrs, "some reason", service);
if (vib != null) {
vib.waitForEnd();
}
@@ -2271,4 +2362,9 @@
}
return predicateResult;
}
+
+ private void denyPermission(String permission) {
+ doThrow(new SecurityException()).when(mContextSpy)
+ .enforceCallingOrSelfPermission(eq(permission), anyString());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 8fadecd..e13dc3e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -65,6 +65,12 @@
KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
{"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
KeyEvent.KEYCODE_BACK, 0},
+ {"Meta + `(grave) -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_GRAVE},
+ KeyboardLogEvent.BACK, KeyEvent.KEYCODE_GRAVE, META_ON},
+ {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+ KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
+ {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+ KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON},
{"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
{"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index bcb0c6b..0989db4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -59,6 +59,7 @@
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.testing.DexmakerShareClassLoaderRule;
+import android.util.Pair;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
@@ -128,6 +129,8 @@
private ActivityManagerInternal mAmInternal;
@Mock
private LockTaskController mLockTaskController;
+ @Mock
+ private TaskDisplayArea mTaskDisplayArea;
private ActivityStartInterceptor mInterceptor;
private ActivityInfo mAInfo = new ActivityInfo();
@@ -139,8 +142,8 @@
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mService.mAmInternal = mAmInternal;
- mInterceptor = new ActivityStartInterceptor(
- mService, mSupervisor, mRootWindowContainer, mContext);
+ mService.mRootWindowContainer = mRootWindowContainer;
+ mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext);
mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID,
TEST_START_FLAGS, TEST_CALLING_PACKAGE, null);
@@ -201,7 +204,7 @@
.thenReturn(PLATFORM_PACKAGE_NAME);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
// THEN the returned intent is the admin support intent
assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent);
@@ -212,7 +215,7 @@
final String suspendingPackage = "com.test.suspending.package";
final SuspendDialogInfo dialogInfo = suspendPackage(suspendingPackage);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
// Check intent parameters
assertEquals(dialogInfo,
@@ -243,7 +246,7 @@
TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
.thenReturn(false);
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
.filterEquals(mInterceptor.mIntent));
@@ -257,7 +260,8 @@
when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
// THEN calling intercept returns false because package also has to be suspended.
- assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertFalse(
+ mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
}
@Test
@@ -268,7 +272,7 @@
when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
// THEN the returned intent is the quiet mode intent
assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
@@ -284,7 +288,7 @@
when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
// THEN the returned intent is the quiet mode intent
assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
@@ -300,7 +304,7 @@
when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
// THEN the returned intent is the quiet mode intent
assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
@@ -313,7 +317,7 @@
when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
// THEN calling intercept returns true
- mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
// THEN the returned intent is the confirm credentials intent
assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
@@ -329,7 +333,7 @@
mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
// THEN calling intercept returns true
- mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null, null);
// THEN the returned intent is original intent
assertSame(originalIntent, mInterceptor.mIntent);
@@ -345,7 +349,7 @@
mAInfo.directBootAware = false;
// THEN calling intercept returns true
- mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
// THEN the returned intent is the confirm credentials intent
assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
@@ -362,7 +366,7 @@
mAInfo.directBootAware = true;
// THEN calling intercept returns true
- mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null, null);
// THEN the returned intent is original intent
assertSame(originalIntent, mInterceptor.mIntent);
@@ -375,7 +379,7 @@
.thenReturn("This app is bad");
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
// THEN the returned intent is the harmful app warning intent
assertEquals(HarmfulAppWarningActivity.class.getName(),
@@ -383,11 +387,40 @@
}
@Test
+ public void testHomeIntentInterception() {
+ // GIVEN a primary home intent and a display area that doesn't support it but supports
+ // secondary home activities
+ Intent originalIntent = new Intent(Intent.ACTION_MAIN);
+ originalIntent.addCategory(Intent.CATEGORY_HOME);
+
+ Intent expectedIntent = new Intent(Intent.ACTION_MAIN);
+ expectedIntent.addCategory(Intent.CATEGORY_SECONDARY_HOME);
+ expectedIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final int secondaryDisplayId = 7;
+ when(mTaskDisplayArea.getDisplayId()).thenReturn(secondaryDisplayId);
+ when(mRootWindowContainer.shouldPlacePrimaryHomeOnDisplay(eq(secondaryDisplayId)))
+ .thenReturn(false);
+ when(mRootWindowContainer.shouldPlaceSecondaryHomeOnDisplayArea(eq(mTaskDisplayArea)))
+ .thenReturn(true);
+ when(mRootWindowContainer.resolveSecondaryHomeActivity(
+ eq(TEST_USER_ID), eq(mTaskDisplayArea)))
+ .thenReturn(Pair.create(null, expectedIntent));
+
+ // THEN calling intercept returns true
+ assertTrue(mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0,
+ null, mTaskDisplayArea));
+
+ // THEN the returned intent is the secondary home intent
+ assertSame(expectedIntent, mInterceptor.mIntent);
+ }
+
+ @Test
public void testNoInterception() {
// GIVEN that none of the interception conditions are met
// THEN calling intercept returns false
- assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
}
public void addMockInterceptorCallback(
@@ -420,7 +453,7 @@
new Intent("android.test.foo"),
ActivityOptions.makeBasic().setLaunchDisplayId(3));
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
}
@@ -429,7 +462,7 @@
public void testInterceptionCallback_singleCallbackReturnsNull() {
addMockInterceptorCallback(null, null);
- assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
}
@Test
@@ -437,7 +470,7 @@
addMockInterceptorCallback(null, null);
addMockInterceptorCallback(new Intent("android.test.second"), null);
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
assertEquals("android.test.second", mInterceptor.mIntent.getAction());
}
@@ -447,7 +480,7 @@
new Intent("android.test.foo"),
ActivityOptions.makeBasic().setLaunchDisplayId(3), true);
ActivityInfo aInfo = mAInfo;
- assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null, null));
assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
assertEquals(aInfo, mInterceptor.mAInfo); // mAInfo should not be resolved
@@ -459,7 +492,7 @@
new Intent("android.test.foo"),
ActivityOptions.makeBasic().setLaunchDisplayId(3));
ActivityInfo aInfo = mAInfo;
- assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null, null));
assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
assertNotEquals(aInfo, mInterceptor.mAInfo); // mAInfo should be resolved after intercept
@@ -488,7 +521,7 @@
when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
verify(spyCallback, times(1)).onInterceptActivityLaunch(
any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
@@ -505,7 +538,7 @@
when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
Intent intent = new Intent().setPackage(sandboxPackageNameMock);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
verify(spyCallback, times(1)).onInterceptActivityLaunch(
any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
@@ -522,7 +555,7 @@
when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
Intent intent = new Intent().setComponent(new ComponentName(sandboxPackageNameMock, ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
verify(spyCallback, times(1)).onInterceptActivityLaunch(
any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
@@ -539,23 +572,23 @@
when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
// Intent: null
- mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
// Action: null, Package: null, ComponentName: null
Intent intent = new Intent();
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
// Wrong Action
intent = new Intent().setAction(Intent.ACTION_VIEW);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
// Wrong Package
intent = new Intent().setPackage("Random");
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
// Wrong ComponentName's package
intent = new Intent().setComponent(new ComponentName("Random", ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+ mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
verify(spyCallback, never()).onInterceptActivityLaunch(
any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index cb92cc5..ae87e38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1662,9 +1662,10 @@
@Test
public void testResultCanceledWhenNotAllowedStartingActivity() {
+ final Task task = new TaskBuilder(mSupervisor).build();
final ActivityStarter starter = prepareStarter(0, false);
final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
- final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build();
targetRecord.resultTo = sourceRecord;
// Abort the activity start and ensure the sourceRecord gets the result (RESULT_CANCELED).
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index e1fc0cf..80e169d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -250,4 +250,42 @@
times(expectedTime)).setLetterboxPositionForVerticalReachability(halfFoldPose,
expected);
}
+
+ @Test
+ public void test_letterboxPositionWhenReachabilityEnabledIsReset() {
+ // Check that horizontal reachability is set with correct arguments
+ mLetterboxConfiguration.resetPersistentLetterboxPositionForHorizontalReachability();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ false /* forBookMode */,
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER);
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ true /* forBookMode */,
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+
+ // Check that vertical reachability is set with correct arguments
+ mLetterboxConfiguration.resetPersistentLetterboxPositionForVerticalReachability();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ false /* forTabletopMode */,
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER);
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ true /* forTabletopMode */,
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ }
+
+ @Test
+ public void test_lettterboxPositionWhenReachabilityEnabledIsSet() {
+ // Check that horizontal reachability is set with correct arguments
+ mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability(
+ false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ false /* forBookMode */,
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+
+ // Check that vertical reachability is set with correct arguments
+ mLetterboxConfiguration.setPersistentLetterboxPositionForVerticalReachability(
+ false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ false /* forTabletopMode */,
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 739737e..07cfbf0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -678,6 +678,39 @@
WINDOWING_MODE_FULLSCREEN);
}
+ @Test
+ public void testInheritsSourceTaskWindowingModeWhenActivityIsInDifferentWindowingMode() {
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FULLSCREEN);
+ final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+ source.setWindowingMode(WINDOWING_MODE_PINNED);
+ source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).calculate());
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void testDoesNotInheritsSourceTaskWindowingModeWhenActivityIsInFreeformWindowingMode() {
+ // The activity could end up in different windowing mode state after calling finish()
+ // while the task would still hold the WINDOWING_MODE_PINNED state, or in other words
+ // be still in the Picture in Picture mode.
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FULLSCREEN);
+ final ActivityRecord source = createSourceActivity(fullscreenDisplay);
+ source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ source.getTask().setWindowingMode(WINDOWING_MODE_PINNED);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).calculate());
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
@Test
public void testKeepsPictureInPictureLaunchModeInOptions() {
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index e51696e..7d9b379 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,6 +32,7 @@
import android.os.ParcelFileDescriptor;
import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.flags.Flags;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -720,6 +722,7 @@
* The underlying connection was added as a transactional call via the
* {@link TelecomManager#addCall} API.
*/
+ @FlaggedApi(Flags.FLAG_VOIP_APP_ACTIONS_SUPPORT)
public static final int PROPERTY_IS_TRANSACTIONAL = 0x00008000;
//******************************************************************************************
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0bb75d8..ac8200a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9456,6 +9456,19 @@
"carrier_supported_satellite_services_per_provider_bundle";
/**
+ * This config enables modem to scan satellite PLMNs specified as per
+ * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE} and attach to same
+ * in case cellular networks are not enabled. This will need specific agreement between
+ * satellite provider and the carrier before enabling this flag.
+ *
+ * The default value is false.
+ *
+ * @hide
+ */
+ public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL =
+ "satellite_attach_supported_bool";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -10465,6 +10478,7 @@
sDefaults.putPersistableBundle(
KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
PersistableBundle.EMPTY);
+ sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 75b5f55..5f6c14a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -44,10 +44,14 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
@@ -79,6 +83,23 @@
@Nullable private final Context mContext;
/**
+ * Create a new SatelliteManager object pinned to the given subscription ID.
+ * This is needed only to handle carrier specific satellite features.
+ * For eg: requestSatelliteAttachEnabledForCarrier and
+ * requestIsSatelliteAttachEnabledForCarrier
+ *
+ * @return a SatelliteManager that uses the given subId for all satellite activities.
+ * @throws IllegalArgumentException if the subscription is invalid.
+ * @hide
+ */
+ public SatelliteManager createForSubscriptionId(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid subscription ID");
+ }
+ return new SatelliteManager(mContext, subId);
+ }
+
+ /**
* Create an instance of the SatelliteManager.
*
* @param context The context the SatelliteManager belongs to.
@@ -791,6 +812,27 @@
public @interface DatagramType {}
/**
+ * Satellite communication restricted by user.
+ * @hide
+ */
+ public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0;
+
+ /**
+ * Satellite communication restricted by geolocation. This can be
+ * triggered based upon geofence input provided by carrier to enable or disable satellite.
+ * @hide
+ */
+ public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
+
+ /** @hide */
+ @IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = {
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SatelliteCommunicationRestrictionReason {}
+
+ /**
* Start receiving satellite transmission updates.
* This can be called by the pointing UI when the user starts pointing to the satellite.
* Modem should continue to report the pointing input as the device or satellite moves.
@@ -1559,6 +1601,182 @@
}
}
+ /**
+ * User request to enable or disable carrier supported satellite plmn scan and attach by modem.
+ * <p>
+ * This API should be called by only settings app to pass down the user input for
+ * enabling/disabling satellite. This user input will be persisted across device reboots.
+ * <p>
+ * Satellite will be enabled only when the following conditions are met:
+ * <ul>
+ * <li>Users want to enable it.</li>
+ * <li>There is no satellite communication restriction, which is added by
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}</li>
+ * <li>The carrier config {@link
+ * android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
+ * {@code true}.</li>
+ * </ul>
+ *
+ * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable.
+ * @param executor The executor on which the error code listener will be called.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void requestSatelliteAttachEnabledForCarrier(boolean enableSatellite,
+ @NonNull @CallbackExecutor Executor executor,
+ @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(resultListener);
+
+ if (enableSatellite) {
+ removeSatelliteAttachRestrictionForCarrier(
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
+ } else {
+ addSatelliteAttachRestrictionForCarrier(
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener);
+ }
+ }
+
+ /**
+ * Request to get whether the carrier supported satellite plmn scan and attach by modem is
+ * enabled by user.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+ * will return a {@code boolean} with value {@code true} if the satellite
+ * is enabled and {@code false} otherwise.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+ * will return a {@link SatelliteException} with the {@link SatelliteError}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void requestIsSatelliteAttachEnabledForCarrier(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier();
+ executor.execute(() -> callback.onResult(
+ !restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)));
+ }
+
+ /**
+ * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
+ * by modem.
+ *
+ * @param reason Reason for disallowing satellite communication.
+ * @param executor The executor on which the error code listener will be called.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void addSatelliteAttachRestrictionForCarrier(
+ @SatelliteCommunicationRestrictionReason int reason,
+ @NonNull @CallbackExecutor Executor executor,
+ @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
+ }
+ };
+ telephony.addSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("addSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
+ * by modem.
+ *
+ * @param reason Reason for disallowing satellite communication.
+ * @param executor The executor on which the error code listener will be called.
+ * @param resultListener Listener for the {@link SatelliteError} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void removeSatelliteAttachRestrictionForCarrier(
+ @SatelliteCommunicationRestrictionReason int reason,
+ @NonNull @CallbackExecutor Executor executor,
+ @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
+ }
+ };
+ telephony.removeSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("removeSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get reasons for disallowing satellite attach, as requested by
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
+ *
+ * @return Set of reasons for disallowing satellite communication.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteCommunicationRestrictionReason
+ public @NonNull Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int[] receivedArray =
+ telephony.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+ if (receivedArray.length == 0) {
+ logd("received set is empty, create empty set");
+ return new HashSet<>();
+ } else {
+ return Arrays.stream(receivedArray).boxed().collect(Collectors.toSet());
+ }
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return null;
+ }
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index ea4e2e2..02661de 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -382,4 +382,64 @@
*/
void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback,
in IIntegerConsumer callback);
+
+ /**
+ * Set the non-terrestrial PLMN with lower priority than terrestrial networks.
+ * MCC/MNC broadcast by the non-terrestrial networks may not be included in OPLMNwACT file on
+ * SIM profile. Acquisition of satellite based system is lower priority to terrestrial
+ * networks. UE shall make all attempts to acquire terrestrial service prior to camping on
+ * satellite LTE service.
+ *
+ * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+ * this information to determine the relevant carrier.
+ * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+ * @param resultCallback The callback to receive the error code result of the operation.
+ *
+ * Valid error codes returned:
+ * SatelliteError:NONE
+ * SatelliteError:INVALID_ARGUMENTS
+ * SatelliteError:INVALID_MODEM_STATE
+ * SatelliteError:MODEM_ERR
+ * SatelliteError:NO_RESOURCES
+ * SatelliteError:RADIO_NOT_AVAILABLE
+ * SatelliteError:REQUEST_NOT_SUPPORTED
+ */
+ void setSatellitePlmn(int simSlot, in List<String> plmnList,
+ in IIntegerConsumer resultCallback);
+
+ /**
+ * Enable or disable satellite in the cellular modem associated with a carrier.
+ * Refer setSatellitePlmn for the details of satellite PLMN scanning process.
+ *
+ * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+ * this information to determine the relevant carrier.
+ * @param serial Serial number of request.
+ * @param enable {@code true} to enable satellite, {@code false} to disable satellite.
+ *
+ * Valid errors returned:
+ * SatelliteError:NONE
+ * SatelliteError:INVALID_MODEM_STATE
+ * SatelliteError:MODEM_ERR
+ * SatelliteError:RADIO_NOT_AVAILABLE
+ * SatelliteError:REQUEST_NOT_SUPPORTED
+ */
+ void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled,
+ in IIntegerConsumer callback);
+
+ /**
+ * Check whether satellite is enabled in the cellular modem associated with a carrier.
+ *
+ * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+ * this information to determine the relevant carrier.
+ * @param serial Serial number of request.
+ *
+ * Valid errors returned:
+ * SatelliteError:NONE
+ * SatelliteError:INVALID_MODEM_STATE
+ * SatelliteError:MODEM_ERR
+ * SatelliteError:RADIO_NOT_AVAILABLE
+ * SatelliteError:REQUEST_NOT_SUPPORTED
+ */
+ void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
+ in IBooleanConsumer callback);
}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 17d026c..6451daf 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -25,6 +25,7 @@
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.util.TelephonyUtils;
+import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -212,6 +213,34 @@
"requestTimeForNextSatelliteVisibility");
}
+ @Override
+ public void setSatellitePlmn(int simSlot, List<String> plmnList,
+ IIntegerConsumer errorCallback)
+ throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this
+ .setSatellitePlmn(simSlot, plmnList, errorCallback),
+ "setSatellitePlmn");
+ }
+
+ @Override
+ public void setSatelliteEnabledForCarrier(int simSlot, boolean enableSatellite,
+ IIntegerConsumer errorCallback) throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this
+ .setSatelliteEnabledForCarrier(simSlot, enableSatellite, errorCallback),
+ "setSatelliteEnabledForCarrier");
+ }
+
+ @Override
+ public void requestIsSatelliteEnabledForCarrier(int simSlot, IIntegerConsumer errorCallback,
+ IBooleanConsumer callback) throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this
+ .requestIsSatelliteEnabledForCarrier(simSlot, errorCallback, callback),
+ "requestIsSatelliteEnabledForCarrier");
+ }
+
// Call the methods with a clean calling identity on the executor and wait indefinitely for
// the future to return.
private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -618,4 +647,75 @@
@NonNull IIntegerConsumer callback) {
// stub implementation
}
+
+
+ /**
+ * Set the non-terrestrial PLMN with lower priority than terrestrial networks.
+ * MCC/MNC broadcast by the non-terrestrial networks may not be included in OPLMNwACT file on
+ * SIM profile. Acquisition of satellite based system is lower priority to terrestrial
+ * networks. UE shall make all attempts to acquire terrestrial service prior to camping on
+ * satellite LTE service.
+ * This method must only take effect if {@link #setSatelliteEnabledForCarrier} is {@code true},
+ * and return an error otherwise.
+ *
+ * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
+ * applied. The modem will use this information to determine the relevant carrier.
+ * @param errorCallback The callback to receive the error code result of the operation.
+ * @param plmnList The list of roaming PLMN used for connecting to satellite networks.
+ *
+ * Valid error codes returned:
+ * SatelliteError:NONE
+ * SatelliteError:INVALID_ARGUMENTS
+ * SatelliteError:INVALID_MODEM_STATE
+ * SatelliteError:MODEM_ERR
+ * SatelliteError:NO_RESOURCES
+ * SatelliteError:RADIO_NOT_AVAILABLE
+ * SatelliteError:REQUEST_NOT_SUPPORTED
+ */
+ public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> plmnList,
+ @NonNull IIntegerConsumer errorCallback) {
+ // stub implementation
+ }
+
+ /**
+ * Request to enable or disable carrier supported satellite plmn scan and attach by modem.
+ * Refer {@link #setSatellitePlmn} for the details of satellite PLMN scanning process.
+ *
+ * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
+ * applied. The modem will use this information to determine the relevant carrier.
+ * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite.
+ * @param callback {@code true} to enable satellite, {@code false} to disable satellite.
+ *
+ * Valid errors returned:
+ * SatelliteError:NONE
+ * SatelliteError:INVALID_MODEM_STATE
+ * SatelliteError:MODEM_ERR
+ * SatelliteError:RADIO_NOT_AVAILABLE
+ * SatelliteError:REQUEST_NOT_SUPPORTED
+ */
+ public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
+ @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) {
+ // stub implementation
+ }
+
+ /**
+ * Request to get whether the satellite is enabled in the cellular modem associated with a
+ * carrier.
+ *
+ * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be
+ * applied. The modem will use this information to determine the relevant carrier.
+ * @param errorCallback The callback to receive the error code result of the operation.
+ * @param callback {@code true} to satellite enabled, {@code false} to satellite disabled.
+ *
+ * Valid errors returned:
+ * SatelliteError:NONE
+ * SatelliteError:INVALID_MODEM_STATE
+ * SatelliteError:MODEM_ERR
+ * SatelliteError:RADIO_NOT_AVAILABLE
+ * SatelliteError:REQUEST_NOT_SUPPORTED
+ */
+ public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
+ @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
+ // stub implementation
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 06071fe..3aa5a5a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3033,4 +3033,42 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
List<String> getShaIdFromAllowList(String pkgName, int carrierId);
+
+ /**
+ * Add a restriction reason for disallowing satellite communication.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param reason Reason for disallowing satellite communication for carrier.
+ * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+ * operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void addSatelliteAttachRestrictionForCarrier(int subId, int reason,
+ in IIntegerConsumer callback);
+
+ /**
+ * Remove a restriction reason for disallowing satellite communication.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param reason Reason for disallowing satellite communication.
+ * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+ * operation.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void removeSatelliteAttachRestrictionForCarrier(int subId, int reason,
+ in IIntegerConsumer callback);
+
+ /**
+ * Get reasons for disallowing satellite communication, as requested by
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, int)}.
+ *
+ * @param subId The subId of the subscription to request for.
+ *
+ * @return Set of reasons for disallowing satellite communication.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
}
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index fe2fe0b..08430f2 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@
private static BatteryUsageStats buildBatteryUsageStats() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
diff --git a/tests/ApkVerityTest/Android.bp b/tests/FsVerityTest/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/Android.bp
rename to tests/FsVerityTest/Android.bp
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
similarity index 100%
rename from tests/ApkVerityTest/AndroidTest.xml
rename to tests/FsVerityTest/AndroidTest.xml
diff --git a/tests/ApkVerityTest/FsVerityTestApp/Android.bp b/tests/FsVerityTest/FsVerityTestApp/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/FsVerityTestApp/Android.bp
rename to tests/FsVerityTest/FsVerityTestApp/Android.bp
diff --git a/tests/ApkVerityTest/FsVerityTestApp/AndroidManifest.xml b/tests/FsVerityTest/FsVerityTestApp/AndroidManifest.xml
similarity index 100%
rename from tests/ApkVerityTest/FsVerityTestApp/AndroidManifest.xml
rename to tests/FsVerityTest/FsVerityTestApp/AndroidManifest.xml
diff --git a/tests/ApkVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java b/tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
similarity index 100%
rename from tests/ApkVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
rename to tests/FsVerityTest/FsVerityTestApp/src/com/android/fsverity/Helper.java
diff --git a/tests/ApkVerityTest/OWNERS b/tests/FsVerityTest/OWNERS
similarity index 100%
rename from tests/ApkVerityTest/OWNERS
rename to tests/FsVerityTest/OWNERS
diff --git a/tests/ApkVerityTest/TEST_MAPPING b/tests/FsVerityTest/TEST_MAPPING
similarity index 100%
rename from tests/ApkVerityTest/TEST_MAPPING
rename to tests/FsVerityTest/TEST_MAPPING
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/FsVerityTest/block_device_writer/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/block_device_writer/Android.bp
rename to tests/FsVerityTest/block_device_writer/Android.bp
diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/FsVerityTest/block_device_writer/block_device_writer.cpp
similarity index 100%
rename from tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
rename to tests/FsVerityTest/block_device_writer/block_device_writer.cpp
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/FsVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
similarity index 100%
rename from tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
rename to tests/FsVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
diff --git a/tests/ApkVerityTest/src/com/android/fsverity/FsVerityHostTest.java b/tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
similarity index 100%
rename from tests/ApkVerityTest/src/com/android/fsverity/FsVerityHostTest.java
rename to tests/FsVerityTest/src/com/android/fsverity/FsVerityHostTest.java
diff --git a/tests/ApkVerityTest/testdata/Android.bp b/tests/FsVerityTest/testdata/Android.bp
similarity index 100%
rename from tests/ApkVerityTest/testdata/Android.bp
rename to tests/FsVerityTest/testdata/Android.bp
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.der b/tests/FsVerityTest/testdata/ApkVerityTestCert.der
similarity index 100%
rename from tests/ApkVerityTest/testdata/ApkVerityTestCert.der
rename to tests/FsVerityTest/testdata/ApkVerityTestCert.der
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem b/tests/FsVerityTest/testdata/ApkVerityTestCert.pem
similarity index 100%
rename from tests/ApkVerityTest/testdata/ApkVerityTestCert.pem
rename to tests/FsVerityTest/testdata/ApkVerityTestCert.pem
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem b/tests/FsVerityTest/testdata/ApkVerityTestKey.pem
similarity index 100%
rename from tests/ApkVerityTest/testdata/ApkVerityTestKey.pem
rename to tests/FsVerityTest/testdata/ApkVerityTestKey.pem
diff --git a/tools/hoststubgen/OWNERS b/tools/hoststubgen/OWNERS
new file mode 100644
index 0000000..a8c5321
--- /dev/null
+++ b/tools/hoststubgen/OWNERS
@@ -0,0 +1,3 @@
+omakoto@google.com
+jsharkey@google.com
+jaggies@google.com